summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouwe Maan <douwe@gitlab.com>2018-10-16 22:39:33 +0000
committerDouwe Maan <douwe@gitlab.com>2018-10-16 22:39:33 +0000
commita706b3735ef01072401dd1ee67ba0586a2fdf61c (patch)
treecd16c664e7c0a207c8a2cc7ff0c97d6e5c619b92
parent90056ed25b547537b02b29715e6153d3aab4cc79 (diff)
parent68eafa50e925ac6da60d84a67884c53c13552315 (diff)
downloadgitlab-ce-a706b3735ef01072401dd1ee67ba0586a2fdf61c.tar.gz
Merge branch 'master' into 'dm-document-role-maintainer'
# Conflicts: # doc/development/code_review.md
-rw-r--r--.eslintrc.yml37
-rw-r--r--.rubocop.yml15
-rw-r--r--CHANGELOG.md8
-rw-r--r--CONTRIBUTING.md198
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock29
-rw-r--r--Gemfile.rails5.lock29
-rw-r--r--PROCESS.md1
-rw-r--r--app/assets/images/koding-logo.svg8
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js4
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js8
-rw-r--r--app/assets/javascripts/boards/components/board.js29
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue15
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue9
-rw-r--r--app/assets/javascripts/boards/components/board_delete.js14
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue67
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue7
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js18
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue19
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue3
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue10
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue9
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js24
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue30
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.vue5
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js3
-rw-r--r--app/assets/javascripts/boards/index.js62
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js25
-rw-r--r--app/assets/javascripts/boards/models/issue.js3
-rw-r--r--app/assets/javascripts/boards/models/list.js9
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js19
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js10
-rw-r--r--app/assets/javascripts/clusters/clusters_index.js5
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/banner.vue11
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions_dropdown.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue10
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js4
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_collection.js13
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_factory.js9
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_form.js82
-rw-r--r--app/assets/javascripts/dismissable_callout.js27
-rw-r--r--app/assets/javascripts/due_date_select.js7
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.vue8
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue34
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue13
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/form.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue14
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue5
-rw-r--r--app/assets/javascripts/ide/components/file_templates/bar.vue4
-rw-r--r--app/assets/javascripts/ide/components/file_templates/dropdown.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue8
-rw-r--r--app/assets/javascripts/ide/components/ide_tree.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue2
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue8
-rw-r--r--app/assets/javascripts/ide/components/nav_dropdown_button.vue4
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue4
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue17
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue7
-rw-r--r--app/assets/javascripts/ide/components/shared/tokened_input.vue4
-rw-r--r--app/assets/javascripts/ide/index.js5
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff.js14
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff_worker.js2
-rw-r--r--app/assets/javascripts/ide/stores/actions/merge_request.js94
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js36
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/mutations.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/actions.js3
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue516
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue181
-rw-r--r--app/assets/javascripts/issue_show/components/edit_actions.vue106
-rw-r--r--app/assets/javascripts/issue_show/components/edited.vue51
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue74
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description_template.vue76
-rw-r--r--app/assets/javascripts/issue_show/components/fields/title.vue18
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue142
-rw-r--r--app/assets/javascripts/issue_show/components/locked_warning.vue12
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js6
-rw-r--r--app/assets/javascripts/jobs/components/artifacts_block.vue42
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue38
-rw-r--r--app/assets/javascripts/jobs/components/empty_state.vue64
-rw-r--r--app/assets/javascripts/jobs/components/environments_block.vue210
-rw-r--r--app/assets/javascripts/jobs/components/erased_block.vue40
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue12
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue24
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue117
-rw-r--r--app/assets/javascripts/jobs/components/jobs_container.vue54
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue205
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_detail_row.vue50
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue69
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue40
-rw-r--r--app/assets/javascripts/jobs/job_details_bundle.js8
-rw-r--r--app/assets/javascripts/jobs/store/actions.js11
-rw-r--r--app/assets/javascripts/jobs/store/getters.js4
-rw-r--r--app/assets/javascripts/jobs/store/index.js13
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js13
-rw-r--r--app/assets/javascripts/jobs/store/state.js4
-rw-r--r--app/assets/javascripts/labels_select.js9
-rw-r--r--app/assets/javascripts/lib/ace.js1
-rw-r--r--app/assets/javascripts/lib/utils/ace_utils.js6
-rw-r--r--app/assets/javascripts/lib/utils/ajax_cache.js7
-rw-r--r--app/assets/javascripts/lib/utils/axios_utils.js23
-rw-r--r--app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js10
-rw-r--r--app/assets/javascripts/lib/utils/cache.js2
-rw-r--r--app/assets/javascripts/lib/utils/datefix.js7
-rw-r--r--app/assets/javascripts/lib/utils/notify.js4
-rw-r--r--app/assets/javascripts/lib/utils/pretty_time.js20
-rw-r--r--app/assets/javascripts/lib/utils/regexp.js3
-rw-r--r--app/assets/javascripts/lib/utils/simple_poll.js2
-rw-r--r--app/assets/javascripts/lib/utils/sticky.js24
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js74
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js22
-rw-r--r--app/assets/javascripts/lib/utils/tick_formats.js2
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js25
-rw-r--r--app/assets/javascripts/main.js1
-rw-r--r--app/assets/javascripts/merge_conflicts/components/diff_file_editor.js20
-rw-r--r--app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js2
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js51
-rw-r--r--app/assets/javascripts/milestone_select.js7
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue11
-rw-r--r--app/assets/javascripts/monitoring/components/graph/axis.vue21
-rw-r--r--app/assets/javascripts/monitoring/components/graph/flag.vue3
-rw-r--r--app/assets/javascripts/monitoring/components/graph/track_info.vue1
-rw-r--r--app/assets/javascripts/monitoring/components/graph/track_line.vue1
-rw-r--r--app/assets/javascripts/monitoring/mixins/monitoring_mixins.js4
-rw-r--r--app/assets/javascripts/monitoring/services/monitoring_service.js49
-rw-r--r--app/assets/javascripts/monitoring/utils/measurements.js6
-rw-r--r--app/assets/javascripts/monitoring/utils/multiple_time_series.js7
-rw-r--r--app/assets/javascripts/notebook/cells/code/index.vue72
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue129
-rw-r--r--app/assets/javascripts/notebook/cells/output/html.vue44
-rw-r--r--app/assets/javascripts/notebook/cells/output/image.vue28
-rw-r--r--app/assets/javascripts/notebook/cells/output/index.vue128
-rw-r--r--app/assets/javascripts/notebook/cells/prompt.vue44
-rw-r--r--app/assets/javascripts/notebook/index.vue77
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue1
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue14
-rw-r--r--app/assets/javascripts/pages/groups/edit/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/index.js6
-rw-r--r--app/assets/javascripts/pages/root/index.js5
-rw-r--r--app/assets/javascripts/pages/users/user_overview_block.js15
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js29
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue15
-rw-r--r--app/assets/javascripts/persistent_user_callout.js34
-rw-r--r--app/assets/javascripts/right_sidebar.js2
-rw-r--r--app/assets/javascripts/settings_panels.js5
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue32
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue56
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue52
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue36
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue50
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue50
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue22
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue50
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue32
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue28
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue142
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue206
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue26
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue68
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue142
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue44
-rw-r--r--app/assets/javascripts/vue_shared/components/bar_chart.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/deprecated_modal.vue150
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_warning.vue51
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue56
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue236
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue101
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue48
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue72
-rw-r--r--app/assets/javascripts/vue_shared/components/memory_graph.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue68
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue32
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/panel_resizer.vue156
-rw-r--r--app/assets/javascripts/vue_shared/components/pikaday.vue106
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar/image.vue123
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue94
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue182
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue206
-rw-r--r--app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/toggle_button.vue94
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue2
-rw-r--r--app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js19
-rw-r--r--app/assets/javascripts/vue_shared/models/label.js2
-rw-r--r--app/assets/javascripts/vue_shared/translate.js9
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js2
-rw-r--r--app/assets/stylesheets/application.scss2
-rw-r--r--app/assets/stylesheets/framework/blocks.scss10
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss16
-rw-r--r--app/assets/stylesheets/framework/mixins.scss9
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/boards.scss3
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss17
-rw-r--r--app/assets/stylesheets/pages/environments.scss1
-rw-r--r--app/assets/stylesheets/pages/groups.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss1
-rw-r--r--app/assets/stylesheets/pages/settings.scss4
-rw-r--r--app/assets/stylesheets/performance_bar.scss4
-rw-r--r--app/controllers/admin/health_check_controller.rb7
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/health_controller.rb11
-rw-r--r--app/controllers/koding_controller.rb17
-rw-r--r--app/controllers/projects/artifacts_controller.rb4
-rw-r--r--app/controllers/projects/build_artifacts_controller.rb4
-rw-r--r--app/controllers/projects/clusters/applications_controller.rb36
-rw-r--r--app/controllers/projects/clusters_controller.rb10
-rw-r--r--app/controllers/users_controller.rb4
-rw-r--r--app/finders/branches_finder.rb41
-rw-r--r--app/finders/projects_finder.rb27
-rw-r--r--app/helpers/application_settings_helper.rb39
-rw-r--r--app/helpers/blob_helper.rb2
-rw-r--r--app/helpers/dashboard_helper.rb23
-rw-r--r--app/helpers/preferences_helper.rb36
-rw-r--r--app/helpers/projects_helper.rb16
-rw-r--r--app/helpers/sorting_helper.rb14
-rw-r--r--app/helpers/storage_health_helper.rb34
-rw-r--r--app/mailers/notify.rb1
-rw-r--r--app/models/application_setting.rb26
-rw-r--r--app/models/blob.rb2
-rw-r--r--app/models/clusters/applications/jupyter.rb2
-rw-r--r--app/models/clusters/cluster.rb5
-rw-r--r--app/models/clusters/concerns/application_core.rb2
-rw-r--r--app/models/clusters/concerns/application_status.rb4
-rw-r--r--app/models/concerns/blob_like.rb2
-rw-r--r--app/models/deployment.rb11
-rw-r--r--app/models/environment.rb2
-rw-r--r--app/models/project.rb15
-rw-r--r--app/models/project_services/prometheus_service.rb8
-rw-r--r--app/models/repository.rb18
-rw-r--r--app/models/user.rb2
-rw-r--r--app/models/user_callout.rb3
-rw-r--r--app/presenters/project_presenter.rb52
-rw-r--r--app/serializers/build_details_entity.rb3
-rw-r--r--app/serializers/issue_entity.rb2
-rw-r--r--app/serializers/merge_request_widget_entity.rb2
-rw-r--r--app/services/applications/create_service.rb1
-rw-r--r--app/services/clusters/applications/create_service.rb68
-rw-r--r--app/services/clusters/applications/schedule_installation_service.rb10
-rw-r--r--app/services/clusters/create_service.rb34
-rw-r--r--app/services/clusters/update_service.rb8
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/views/admin/application_settings/_koding.html.haml22
-rw-r--r--app/views/admin/application_settings/_repository_storage.html.haml27
-rw-r--r--app/views/admin/application_settings/repository.html.haml2
-rw-r--r--app/views/admin/application_settings/show.html.haml12
-rw-r--r--app/views/admin/applications/show.html.haml2
-rw-r--r--app/views/admin/groups/_form.html.haml4
-rw-r--r--app/views/admin/health_check/_failing_storages.html.haml15
-rw-r--r--app/views/admin/health_check/show.html.haml3
-rw-r--r--app/views/dashboard/activity.html.haml3
-rw-r--r--app/views/dashboard/groups/index.html.haml3
-rw-r--r--app/views/dashboard/issues.html.haml3
-rw-r--r--app/views/dashboard/merge_requests.html.haml3
-rw-r--r--app/views/dashboard/projects/index.html.haml3
-rw-r--r--app/views/dashboard/projects/starred.html.haml3
-rw-r--r--app/views/dashboard/todos/index.html.haml3
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml14
-rw-r--r--app/views/doorkeeper/applications/show.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml5
-rw-r--r--app/views/explore/projects/index.html.haml3
-rw-r--r--app/views/explore/projects/starred.html.haml3
-rw-r--r--app/views/explore/projects/trending.html.haml3
-rw-r--r--app/views/groups/edit.html.haml24
-rw-r--r--app/views/groups/new.html.haml2
-rw-r--r--app/views/groups/settings/_advanced.html.haml20
-rw-r--r--app/views/groups/settings/_general.html.haml48
-rw-r--r--app/views/groups/settings/_lfs.html.haml15
-rw-r--r--app/views/groups/settings/_permissions.html.haml37
-rw-r--r--app/views/groups/settings/_two_factor_auth.html.haml16
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/koding/index.html.haml6
-rw-r--r--app/views/layouts/koding.html.haml5
-rw-r--r--app/views/layouts/nav/_breadcrumbs.html.haml1
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml1
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/blob/_template_selectors.html.haml10
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml4
-rw-r--r--app/views/projects/buttons/_koding.html.haml3
-rw-r--r--app/views/projects/clusters/_banner.html.haml4
-rw-r--r--app/views/projects/clusters/_gcp_signup_offer_banner.html.haml4
-rw-r--r--app/views/projects/deploy_tokens/_form.html.haml10
-rw-r--r--app/views/projects/deploy_tokens/_index.html.haml2
-rw-r--r--app/views/projects/deploy_tokens/_new_deploy_token.html.haml6
-rw-r--r--app/views/projects/jobs/show.html.haml4
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml4
-rw-r--r--app/views/projects/project_members/_team.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_configuration_banner.html.haml2
-rw-r--r--app/views/projects/show.html.haml4
-rw-r--r--app/views/projects/tree/_tree_header.html.haml2
-rw-r--r--app/views/shared/_allow_request_access.html.haml6
-rw-r--r--app/views/shared/_old_visibility_level.html.haml6
-rw-r--r--app/views/shared/_visibility_level.html.haml30
-rw-r--r--app/views/shared/empty_states/_wikis.html.haml2
-rw-r--r--app/views/shared/projects/_list.html.haml3
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rwxr-xr-xbin/storage_check11
-rw-r--r--changelogs/unreleased/22311-fix-duplicated-key-in-license-management-job.yml5
-rw-r--r--changelogs/unreleased/48684-sort-projects-by-stars-in-groups.yml4
-rw-r--r--changelogs/unreleased/48889-message-for-were-merged-into.yml5
-rw-r--r--changelogs/unreleased/49417-improve-settings-pages-design-by-prioritizing-content-group-settings.yml5
-rw-r--r--changelogs/unreleased/50185-fix-broken-file-name-navigation.yml5
-rw-r--r--changelogs/unreleased/51958-fix-mr-discussion-loading.yml5
-rw-r--r--changelogs/unreleased/51972-prometheus-not-showing-as-installed-even-though-it-is.yml5
-rw-r--r--changelogs/unreleased/52147-loading-state.yml5
-rw-r--r--changelogs/unreleased/52361-fix-file-tree-mobile.yml5
-rw-r--r--changelogs/unreleased/52421-show-canary-no-canary-in-the-performance-bar.yml5
-rw-r--r--changelogs/unreleased/52472-pipeline-endpoint-json.yml5
-rw-r--r--changelogs/unreleased/52477-add-iid-headers-to-emails.yml5
-rw-r--r--changelogs/unreleased/52519-runners-link.yml5
-rw-r--r--changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml5
-rw-r--r--changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml5
-rw-r--r--changelogs/unreleased/52570-erased-block.yml5
-rw-r--r--changelogs/unreleased/52608-sidebar.yml5
-rw-r--r--changelogs/unreleased/52614-update-job-started-check.yml5
-rw-r--r--changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml5
-rw-r--r--changelogs/unreleased/52669-fixes-quick-actions-preview.yml5
-rw-r--r--changelogs/unreleased/52686-project-slug-does-not-auto-populate-in-ie11.yml5
-rw-r--r--changelogs/unreleased/bvl-merge-base-multiple-revisions.yml5
-rw-r--r--changelogs/unreleased/da-fix-does-not-import-projects-over-ssh.yml5
-rw-r--r--changelogs/unreleased/diff-stats-perf-bar.yml5
-rw-r--r--changelogs/unreleased/enable-frozen-string-lib-gitlab.yml5
-rw-r--r--changelogs/unreleased/even-more-frozen-string-lib.yml5
-rw-r--r--changelogs/unreleased/feature-improved-branch-filter-sorting.yml6
-rw-r--r--changelogs/unreleased/fl-update-svgs.yml5
-rw-r--r--changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml5
-rw-r--r--changelogs/unreleased/gt-update-wiki-empty-state.yml5
-rw-r--r--changelogs/unreleased/ide-file-templates-clear.yml5
-rw-r--r--changelogs/unreleased/replace-i-to-icons-in-vue-components.yml5
-rw-r--r--changelogs/unreleased/sh-fix-commit-signatures-error.yml5
-rw-r--r--changelogs/unreleased/sh-remove-koding.yml5
-rw-r--r--changelogs/unreleased/test-usage-ping-in-timeout-case.yml5
-rw-r--r--changelogs/unreleased/zj-circuit-breaker-removal.yml5
-rw-r--r--changelogs/unreleased/zj-remove-linguist.yml5
-rw-r--r--config/routes.rb4
-rw-r--r--config/routes/admin.rb4
-rw-r--r--config/unicorn.rb.example4
-rw-r--r--config/unicorn.rb.example.development4
-rw-r--r--db/post_migrate/20181008200441_remove_circuit_breaker.rb30
-rw-r--r--db/post_migrate/20181013005024_remove_koding_from_application_settings.rb17
-rw-r--r--db/schema.rb9
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/gitaly/index.md6
-rw-r--r--doc/administration/high_availability/nfs.md9
-rw-r--r--doc/administration/img/circuitbreaker_config.pngbin29130 -> 0 bytes
-rw-r--r--doc/administration/img/failing_storage.pngbin16291 -> 0 bytes
-rw-r--r--doc/administration/index.md3
-rw-r--r--doc/administration/integration/koding.md246
-rw-r--r--doc/administration/integration/plantuml.md4
-rw-r--r--doc/administration/job_artifacts.md3
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md3
-rw-r--r--doc/administration/operations/filesystem_benchmarking.md55
-rw-r--r--doc/administration/operations/index.md4
-rw-r--r--doc/administration/repository_storage_paths.md49
-rw-r--r--doc/api/events.md4
-rw-r--r--doc/api/repositories.md2
-rw-r--r--doc/api/repository_storage_health.md75
-rw-r--r--doc/api/settings.md15
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/index.md12
-rw-r--r--doc/ci/examples/php.md2
-rw-r--r--doc/ci/img/pipeline_incremental_rollout.pngbin0 -> 4794 bytes
-rw-r--r--doc/ci/pipelines.md13
-rw-r--r--doc/ci/triggers/README.md7
-rw-r--r--doc/ci/yaml/README.md40
-rw-r--r--doc/development/README.md3
-rw-r--r--doc/development/code_review.md14
-rw-r--r--doc/development/contributing/community_roles.md6
-rw-r--r--doc/development/contributing/design.md33
-rw-r--r--doc/development/contributing/index.md208
-rw-r--r--doc/development/contributing/issue_workflow.md54
-rw-r--r--doc/development/contributing/merge_request_workflow.md32
-rw-r--r--doc/development/contributing/style_guides.md40
-rw-r--r--doc/development/documentation/index.md4
-rw-r--r--doc/development/fe_guide/style_guide_js.md109
-rw-r--r--doc/development/feature_flags.md31
-rw-r--r--doc/development/new_fe_guide/index.md4
-rw-r--r--doc/development/new_fe_guide/modules/dirty_submit.md23
-rw-r--r--doc/development/new_fe_guide/modules/index.md5
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/policy/maintenance.md3
-rw-r--r--doc/topics/autodevops/index.md22
-rw-r--r--doc/update/11.3-to-11.4.md4
-rw-r--r--doc/user/discussions/index.md2
-rw-r--r--doc/user/group/index.md1
-rw-r--r--[-rwxr-xr-x]doc/user/project/img/issue_boards_core.pngbin61230 -> 61230 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/img/issue_boards_premium.pngbin72434 -> 72434 bytes
-rw-r--r--doc/user/project/img/koding_build-in-progress.pngbin21937 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_build-logs.pngbin91364 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_build-success.pngbin73005 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_commit-koding.yml.pngbin86023 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_different-stack-on-mr-try.pngbin93404 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_edit-on-ide.pngbin90638 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_enable-koding.pngbin20291 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_landing.pngbin80985 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_open-gitlab-from-koding.pngbin10851 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_run-in-ide.pngbin22178 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_run-mr-in-ide.pngbin93780 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_set-up-ide.pngbin54062 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_stack-import.pngbin137582 -> 0 bytes
-rw-r--r--doc/user/project/img/koding_start-build.pngbin27925 -> 0 bytes
-rw-r--r--doc/user/project/integrations/services_templates.md6
-rw-r--r--doc/user/project/integrations/webhooks.md13
-rw-r--r--doc/user/project/koding.md131
-rw-r--r--doc/user/project/merge_requests/cherry_pick_changes.md8
-rw-r--r--doc/user/project/merge_requests/img/merge_request_diff_file_navigation.pngbin112288 -> 111544 bytes
-rw-r--r--doc/user/project/merge_requests/index.md6
-rw-r--r--doc/user/project/repository/branches/img/branch_filter_search_box.pngbin0 -> 83225 bytes
-rw-r--r--doc/user/project/repository/branches/index.md18
-rw-r--r--doc/user/project/repository/index.md11
-rw-r--r--doc/user/reserved_names.md1
-rw-r--r--lib/api/circuit_breakers.rb17
-rw-r--r--lib/api/entities.rb6
-rw-r--r--lib/api/repositories.rb9
-rw-r--r--lib/api/settings.rb4
-rw-r--r--lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb2
-rw-r--r--lib/gitaly/server.rb2
-rw-r--r--lib/gitlab/auth/activity.rb2
-rw-r--r--lib/gitlab/auth/database/authentication.rb2
-rw-r--r--lib/gitlab/auth/ip_rate_limiter.rb2
-rw-r--r--lib/gitlab/auth/ldap/access.rb2
-rw-r--r--lib/gitlab/auth/ldap/adapter.rb2
-rw-r--r--lib/gitlab/auth/ldap/auth_hash.rb2
-rw-r--r--lib/gitlab/auth/ldap/authentication.rb2
-rw-r--r--lib/gitlab/auth/ldap/config.rb2
-rw-r--r--lib/gitlab/auth/ldap/dn.rb1
-rw-r--r--lib/gitlab/auth/ldap/ldap_connection_error.rb2
-rw-r--r--lib/gitlab/auth/ldap/person.rb2
-rw-r--r--lib/gitlab/auth/ldap/user.rb2
-rw-r--r--lib/gitlab/auth/o_auth/auth_hash.rb2
-rw-r--r--lib/gitlab/auth/o_auth/authentication.rb2
-rw-r--r--lib/gitlab/auth/o_auth/identity_linker.rb2
-rw-r--r--lib/gitlab/auth/o_auth/provider.rb2
-rw-r--r--lib/gitlab/auth/o_auth/session.rb2
-rw-r--r--lib/gitlab/auth/o_auth/user.rb2
-rw-r--r--lib/gitlab/auth/omniauth_identity_linker_base.rb2
-rw-r--r--lib/gitlab/auth/request_authenticator.rb2
-rw-r--r--lib/gitlab/auth/result.rb5
-rw-r--r--lib/gitlab/auth/saml/auth_hash.rb2
-rw-r--r--lib/gitlab/auth/saml/config.rb2
-rw-r--r--lib/gitlab/auth/saml/identity_linker.rb2
-rw-r--r--lib/gitlab/auth/saml/user.rb2
-rw-r--r--lib/gitlab/auth/too_many_ips.rb2
-rw-r--r--lib/gitlab/auth/unique_ips_limiter.rb2
-rw-r--r--lib/gitlab/auth/user_access_denied_reason.rb2
-rw-r--r--lib/gitlab/auth/user_auth_finders.rb2
-rw-r--r--lib/gitlab/badge/base.rb2
-rw-r--r--lib/gitlab/badge/coverage/metadata.rb2
-rw-r--r--lib/gitlab/badge/coverage/report.rb2
-rw-r--r--lib/gitlab/badge/coverage/template.rb2
-rw-r--r--lib/gitlab/badge/metadata.rb2
-rw-r--r--lib/gitlab/badge/pipeline/metadata.rb2
-rw-r--r--lib/gitlab/badge/pipeline/status.rb2
-rw-r--r--lib/gitlab/badge/pipeline/template.rb2
-rw-r--r--lib/gitlab/badge/template.rb2
-rw-r--r--lib/gitlab/bare_repository_import/importer.rb7
-rw-r--r--lib/gitlab/bare_repository_import/repository.rb7
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb2
-rw-r--r--lib/gitlab/bitbucket_import/project_creator.rb2
-rw-r--r--lib/gitlab/bitbucket_server_import/project_creator.rb2
-rw-r--r--lib/gitlab/blob_helper.rb145
-rw-r--r--lib/gitlab/cache/ci/project_pipeline_status.rb2
-rw-r--r--lib/gitlab/cache/request_cache.rb2
-rw-r--r--lib/gitlab/checks/change_access.rb2
-rw-r--r--lib/gitlab/checks/commit_check.rb2
-rw-r--r--lib/gitlab/checks/force_push.rb2
-rw-r--r--lib/gitlab/checks/lfs_integrity.rb2
-rw-r--r--lib/gitlab/checks/matching_merge_request.rb2
-rw-r--r--lib/gitlab/checks/post_push_message.rb2
-rw-r--r--lib/gitlab/checks/project_created.rb2
-rw-r--r--lib/gitlab/checks/project_moved.rb2
-rw-r--r--lib/gitlab/ci/templates/Android.gitlab-ci.yml40
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml4
-rw-r--r--lib/gitlab/conflict/file.rb1
-rw-r--r--lib/gitlab/file_detector.rb1
-rw-r--r--lib/gitlab/git/blob.rb8
-rw-r--r--lib/gitlab/git/blob_snippet.rb34
-rw-r--r--lib/gitlab/git/repository.rb8
-rw-r--r--lib/gitlab/git/storage.rb25
-rw-r--r--lib/gitlab/git/storage/checker.rb120
-rw-r--r--lib/gitlab/git/storage/circuit_breaker.rb78
-rw-r--r--lib/gitlab/git/storage/circuit_breaker_settings.rb37
-rw-r--r--lib/gitlab/git/storage/failure_info.rb39
-rw-r--r--lib/gitlab/git/storage/forked_storage_check.rb65
-rw-r--r--lib/gitlab/git/storage/health.rb92
-rw-r--r--lib/gitlab/git/storage/null_circuit_breaker.rb50
-rw-r--r--lib/gitlab/gon_helper.rb15
-rw-r--r--lib/gitlab/hook_data/base_builder.rb11
-rw-r--r--lib/gitlab/language_data.rb33
-rw-r--r--lib/gitlab/path_regex.rb1
-rw-r--r--lib/gitlab/storage_check.rb11
-rw-r--r--lib/gitlab/storage_check/cli.rb71
-rw-r--r--lib/gitlab/storage_check/gitlab_caller.rb39
-rw-r--r--lib/gitlab/storage_check/option_parser.rb39
-rw-r--r--lib/gitlab/storage_check/response.rb77
-rw-r--r--lib/google_api/auth.rb2
-rw-r--r--lib/google_api/cloud_platform/client.rb2
-rw-r--r--lib/haml_lint/inline_javascript.rb5
-rw-r--r--lib/json_web_token/rsa_token.rb2
-rw-r--r--lib/json_web_token/token.rb2
-rw-r--r--lib/mattermost/client.rb2
-rw-r--r--lib/mattermost/command.rb2
-rw-r--r--lib/mattermost/error.rb2
-rw-r--r--lib/mattermost/session.rb2
-rw-r--r--lib/mattermost/team.rb2
-rw-r--r--lib/microsoft_teams/activity.rb2
-rw-r--r--lib/microsoft_teams/notifier.rb2
-rw-r--r--lib/object_storage/direct_upload.rb2
-rw-r--r--lib/omni_auth/strategies/bitbucket.rb2
-rw-r--r--lib/omni_auth/strategies/jwt.rb2
-rw-r--r--lib/peek/rblineprof/custom_controller_helpers.rb6
-rw-r--r--lib/peek/views/gitaly.rb2
-rw-r--r--lib/peek/views/host.rb7
-rw-r--r--lib/rouge/formatters/html_gitlab.rb2
-rw-r--r--lib/rouge/plugins/common_mark.rb2
-rw-r--r--lib/rspec_flaky/config.rb2
-rw-r--r--lib/rspec_flaky/example.rb2
-rw-r--r--lib/rspec_flaky/flaky_example.rb2
-rw-r--r--lib/rspec_flaky/flaky_examples_collection.rb2
-rw-r--r--lib/rspec_flaky/listener.rb2
-rw-r--r--lib/rspec_flaky/report.rb2
-rw-r--r--lib/system_check/app/active_users_check.rb2
-rw-r--r--lib/system_check/app/database_config_exists_check.rb2
-rw-r--r--lib/system_check/app/git_config_check.rb2
-rw-r--r--lib/system_check/app/git_user_default_ssh_config_check.rb2
-rw-r--r--lib/system_check/app/git_version_check.rb2
-rw-r--r--lib/system_check/app/gitlab_config_exists_check.rb2
-rw-r--r--lib/system_check/app/gitlab_config_up_to_date_check.rb2
-rw-r--r--lib/system_check/app/init_script_exists_check.rb2
-rw-r--r--lib/system_check/app/init_script_up_to_date_check.rb2
-rw-r--r--lib/system_check/app/log_writable_check.rb2
-rw-r--r--lib/system_check/app/migrations_are_up_check.rb2
-rw-r--r--lib/system_check/app/orphaned_group_members_check.rb2
-rw-r--r--lib/system_check/app/projects_have_namespace_check.rb2
-rw-r--r--lib/system_check/app/redis_version_check.rb2
-rw-r--r--lib/system_check/app/ruby_version_check.rb2
-rw-r--r--lib/system_check/app/tmp_writable_check.rb2
-rw-r--r--lib/system_check/app/uploads_directory_exists_check.rb2
-rw-r--r--lib/system_check/app/uploads_path_permission_check.rb2
-rw-r--r--lib/system_check/app/uploads_path_tmp_permission_check.rb2
-rw-r--r--lib/system_check/base_check.rb2
-rw-r--r--lib/system_check/helpers.rb2
-rw-r--r--lib/system_check/incoming_email/foreman_configured_check.rb2
-rw-r--r--lib/system_check/incoming_email/imap_authentication_check.rb2
-rw-r--r--lib/system_check/incoming_email/initd_configured_check.rb2
-rw-r--r--lib/system_check/incoming_email/mail_room_running_check.rb2
-rw-r--r--lib/system_check/orphans/namespace_check.rb2
-rw-r--r--lib/system_check/orphans/repository_check.rb2
-rw-r--r--lib/system_check/simple_executor.rb2
-rw-r--r--locale/ar_SA/gitlab.po12
-rw-r--r--locale/bg/gitlab.po12
-rw-r--r--locale/ca_ES/gitlab.po12
-rw-r--r--locale/cs_CZ/gitlab.po12
-rw-r--r--locale/da_DK/gitlab.po12
-rw-r--r--locale/de/gitlab.po12
-rw-r--r--locale/en/gitlab.po3
-rw-r--r--locale/eo/gitlab.po12
-rw-r--r--locale/es/gitlab.po12
-rw-r--r--locale/et_EE/gitlab.po12
-rw-r--r--locale/fil_PH/gitlab.po12
-rw-r--r--locale/fr/gitlab.po12
-rw-r--r--locale/gitlab.pot140
-rw-r--r--locale/gl_ES/gitlab.po12
-rw-r--r--locale/he_IL/gitlab.po12
-rw-r--r--locale/id_ID/gitlab.po12
-rw-r--r--locale/it/gitlab.po12
-rw-r--r--locale/ja/gitlab.po12
-rw-r--r--locale/ko/gitlab.po12
-rw-r--r--locale/mn_MN/gitlab.po12
-rw-r--r--locale/nb_NO/gitlab.po12
-rw-r--r--locale/nl_NL/gitlab.po12
-rw-r--r--locale/pl_PL/gitlab.po12
-rw-r--r--locale/pt_BR/gitlab.po12
-rw-r--r--locale/ro_RO/gitlab.po12
-rw-r--r--locale/ru/gitlab.po12
-rw-r--r--locale/sq_AL/gitlab.po12
-rw-r--r--locale/tr_TR/gitlab.po12
-rw-r--r--locale/uk/gitlab.po12
-rw-r--r--locale/zh_CN/gitlab.po12
-rw-r--r--locale/zh_HK/gitlab.po12
-rw-r--r--locale/zh_TW/gitlab.po12
-rw-r--r--package.json9
-rw-r--r--qa/Dockerfile12
-rw-r--r--qa/qa.rb25
-rw-r--r--qa/qa/factory/resource/deploy_token.rb48
-rw-r--r--qa/qa/factory/resource/file.rb4
-rw-r--r--qa/qa/page/README.md38
-rw-r--r--qa/qa/page/base.rb19
-rw-r--r--qa/qa/page/component/clone_panel.rb4
-rw-r--r--qa/qa/page/component/dropdown_filter.rb18
-rw-r--r--qa/qa/page/component/users_select.rb14
-rw-r--r--qa/qa/page/dashboard/groups.rb6
-rw-r--r--qa/qa/page/dashboard/projects.rb2
-rw-r--r--qa/qa/page/file/form.rb34
-rw-r--r--qa/qa/page/file/shared/commit_message.rb2
-rw-r--r--qa/qa/page/file/show.rb6
-rw-r--r--qa/qa/page/group/new.rb10
-rw-r--r--qa/qa/page/group/show.rb2
-rw-r--r--qa/qa/page/issuable/sidebar.rb4
-rw-r--r--qa/qa/page/layout/banner.rb2
-rw-r--r--qa/qa/page/main/login.rb9
-rw-r--r--qa/qa/page/main/menu.rb10
-rw-r--r--qa/qa/page/main/oauth.rb2
-rw-r--r--qa/qa/page/main/sign_up.rb36
-rw-r--r--qa/qa/page/merge_request/show.rb6
-rw-r--r--qa/qa/page/profile/menu.rb8
-rw-r--r--qa/qa/page/profile/personal_access_tokens.rb8
-rw-r--r--qa/qa/page/project/activity.rb2
-rw-r--r--qa/qa/page/project/fork/new.rb2
-rw-r--r--qa/qa/page/project/import/github.rb12
-rw-r--r--qa/qa/page/project/issue/index.rb2
-rw-r--r--qa/qa/page/project/issue/new.rb6
-rw-r--r--qa/qa/page/project/issue/show.rb10
-rw-r--r--qa/qa/page/project/job/show.rb6
-rw-r--r--qa/qa/page/project/menu.rb35
-rw-r--r--qa/qa/page/project/new.rb16
-rw-r--r--qa/qa/page/project/operations/kubernetes/add.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/add_existing.rb10
-rw-r--r--qa/qa/page/project/operations/kubernetes/index.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/show.rb8
-rw-r--r--qa/qa/page/project/pipeline/index.rb2
-rw-r--r--qa/qa/page/project/pipeline/show.rb10
-rw-r--r--qa/qa/page/project/settings/advanced.rb6
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb10
-rw-r--r--qa/qa/page/project/settings/common.rb2
-rw-r--r--qa/qa/page/project/settings/deploy_keys.rb12
-rw-r--r--qa/qa/page/project/settings/deploy_tokens.rb64
-rw-r--r--qa/qa/page/project/settings/members.rb27
-rw-r--r--qa/qa/page/project/settings/repository.rb6
-rw-r--r--qa/qa/page/project/settings/runners.rb6
-rw-r--r--qa/qa/page/project/settings/secret_variables.rb10
-rw-r--r--qa/qa/page/project/show.rb28
-rw-r--r--qa/qa/page/project/web_ide/edit.rb79
-rw-r--r--qa/qa/page/project/wiki/edit.rb6
-rw-r--r--qa/qa/page/project/wiki/new.rb12
-rw-r--r--qa/qa/page/project/wiki/show.rb2
-rw-r--r--qa/qa/runtime/browser.rb4
-rw-r--r--qa/qa/runtime/env.rb4
-rw-r--r--qa/qa/runtime/fixtures.rb19
-rw-r--r--qa/qa/runtime/path.rb13
-rw-r--r--qa/qa/scenario/test/integration/instance_saml.rb13
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb17
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb27
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb77
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb87
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb23
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb2
-rw-r--r--qa/qa/vendor/saml_idp/page/base.rb14
-rw-r--r--qa/qa/vendor/saml_idp/page/login.rb19
-rw-r--r--qa/spec/page/base_spec.rb6
-rw-r--r--qa/spec/page/view_spec.rb4
-rw-r--r--qa/spec/scenario/test/integration/instance_saml_spec.rb9
-rw-r--r--rubocop/cop/qa/element_with_pattern.rb39
-rw-r--r--rubocop/qa_helpers.rb11
-rw-r--r--rubocop/rubocop.rb1
-rwxr-xr-xscripts/trigger-build8
-rw-r--r--spec/bin/storage_check_spec.rb13
-rw-r--r--spec/controllers/admin/health_check_controller_spec.rb12
-rw-r--r--spec/controllers/application_controller_spec.rb50
-rw-r--r--spec/controllers/health_controller_spec.rb42
-rw-r--r--spec/controllers/projects/clusters_controller_spec.rb286
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/merge_requests/conflicts_controller_spec.rb1
-rw-r--r--spec/features/admin/admin_health_check_spec.rb26
-rw-r--r--spec/features/groups/group_settings_spec.rb2
-rw-r--r--spec/features/groups/share_lock_spec.rb4
-rw-r--r--spec/features/groups/show_spec.rb21
-rw-r--r--spec/features/groups_spec.rb5
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb12
-rw-r--r--spec/features/projects/jobs_spec.rb75
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb35
-rw-r--r--spec/features/projects_spec.rb17
-rw-r--r--spec/features/security/dashboard_access_spec.rb14
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb2
-rw-r--r--spec/features/users/overview_spec.rb31
-rw-r--r--spec/finders/branches_finder_spec.rb33
-rw-r--r--spec/fixtures/api/schemas/deployment.json27
-rw-r--r--spec/fixtures/api/schemas/entities/commit.json5
-rw-r--r--spec/fixtures/api/schemas/job/job_details.json6
-rw-r--r--spec/fixtures/valid.po3
-rw-r--r--spec/helpers/preferences_helper_spec.rb7
-rw-r--r--spec/helpers/storage_health_helper_spec.rb20
-rw-r--r--spec/javascripts/.eslintrc.yml3
-rw-r--r--spec/javascripts/awards_handler_spec.js16
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js2
-rw-r--r--spec/javascripts/boards/board_blank_state_spec.js16
-rw-r--r--spec/javascripts/boards/board_card_spec.js22
-rw-r--r--spec/javascripts/boards/board_list_spec.js7
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js10
-rw-r--r--spec/javascripts/boards/boards_store_spec.js96
-rw-r--r--spec/javascripts/boards/components/board_spec.js6
-rw-r--r--spec/javascripts/boards/issue_card_spec.js1
-rw-r--r--spec/javascripts/boards/issue_spec.js4
-rw-r--r--spec/javascripts/boards/list_spec.js12
-rw-r--r--spec/javascripts/boards/mock_data.js3
-rw-r--r--spec/javascripts/bootstrap_jquery_spec.js2
-rw-r--r--spec/javascripts/datetime_utility_spec.js6
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js15
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js8
-rw-r--r--spec/javascripts/diffs/components/diff_line_gutter_content_spec.js2
-rw-r--r--spec/javascripts/diffs/components/inline_diff_view_spec.js4
-rw-r--r--spec/javascripts/dirty_submit/dirty_submit_collection_spec.js25
-rw-r--r--spec/javascripts/dirty_submit/dirty_submit_factory_spec.js18
-rw-r--r--spec/javascripts/dirty_submit/dirty_submit_form_spec.js21
-rw-r--r--spec/javascripts/dirty_submit/helper.js31
-rw-r--r--spec/javascripts/emoji_spec.js38
-rw-r--r--spec/javascripts/environments/environments_app_spec.js14
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js2
-rw-r--r--spec/javascripts/filtered_search/filtered_search_token_keys_spec.js14
-rw-r--r--spec/javascripts/fixtures/groups.rb10
-rw-r--r--spec/javascripts/gl_dropdown_spec.js4
-rw-r--r--spec/javascripts/graphs/stat_graph_contributors_util_spec.js2
-rw-r--r--spec/javascripts/groups/components/group_item_spec.js2
-rw-r--r--spec/javascripts/groups/components/groups_spec.js2
-rw-r--r--spec/javascripts/groups/components/item_stats_spec.js10
-rw-r--r--spec/javascripts/groups/components/item_stats_value_spec.js4
-rw-r--r--spec/javascripts/groups/store/groups_store_spec.js12
-rw-r--r--spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js8
-rw-r--r--spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js2
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js9
-rw-r--r--spec/javascripts/jobs/components/job_log_controllers_spec.js1
-rw-r--r--spec/javascripts/jobs/components/sidebar_spec.js4
-rw-r--r--spec/javascripts/jobs/components/stages_dropdown_spec.js20
-rw-r--r--spec/javascripts/jobs/store/actions_spec.js17
-rw-r--r--spec/javascripts/jobs/store/getters_spec.js10
-rw-r--r--spec/javascripts/jobs/store/mutations_spec.js27
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js16
-rw-r--r--spec/javascripts/line_highlighter_spec.js18
-rw-r--r--spec/javascripts/new_branch_spec.js24
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js1
-rw-r--r--spec/javascripts/notes_spec.js6
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js2
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js4
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js10
-rw-r--r--spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js4
-rw-r--r--spec/javascripts/reports/components/report_section_spec.js1
-rw-r--r--spec/javascripts/right_sidebar_spec.js32
-rw-r--r--spec/javascripts/search_autocomplete_spec.js6
-rw-r--r--spec/javascripts/settings_panels_spec.js30
-rw-r--r--spec/javascripts/syntax_highlight_spec.js1
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js10
-rw-r--r--spec/javascripts/vue_shared/components/memory_graph_spec.js4
-rw-r--r--spec/lib/gitlab/bitbucket_import/project_creator_spec.rb4
-rw-r--r--spec/lib/gitlab/blob_helper_spec.rb125
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb11
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/factory_spec.rb68
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb5
-rw-r--r--spec/lib/gitlab/file_detector_spec.rb4
-rw-r--r--spec/lib/gitlab/git/blob_snippet_spec.rb19
-rw-r--r--spec/lib/gitlab/git/storage/checker_spec.rb132
-rw-r--r--spec/lib/gitlab/git/storage/circuit_breaker_spec.rb187
-rw-r--r--spec/lib/gitlab/git/storage/failure_info_spec.rb70
-rw-r--r--spec/lib/gitlab/git/storage/forked_storage_check_spec.rb73
-rw-r--r--spec/lib/gitlab/git/storage/health_spec.rb58
-rw-r--r--spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb70
-rw-r--r--spec/lib/gitlab/gon_helper_spec.rb32
-rw-r--r--spec/lib/gitlab/hook_data/base_builder_spec.rb129
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb5
-rw-r--r--spec/lib/gitlab/hook_data/merge_request_builder_spec.rb7
-rw-r--r--spec/lib/gitlab/language_data_spec.rb22
-rw-r--r--spec/lib/gitlab/storage_check/cli_spec.rb19
-rw-r--r--spec/lib/gitlab/storage_check/gitlab_caller_spec.rb46
-rw-r--r--spec/lib/gitlab/storage_check/option_parser_spec.rb31
-rw-r--r--spec/lib/gitlab/storage_check/response_spec.rb54
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb7
-rw-r--r--spec/models/application_setting_spec.rb13
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb2
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb2
-rw-r--r--spec/models/clusters/applications/runner_spec.rb2
-rw-r--r--spec/models/clusters/cluster_spec.rb7
-rw-r--r--spec/models/deployment_spec.rb23
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb23
-rw-r--r--spec/models/project_spec.rb61
-rw-r--r--spec/models/repository_spec.rb27
-rw-r--r--spec/presenters/project_presenter_spec.rb31
-rw-r--r--spec/requests/api/circuit_breakers_spec.rb19
-rw-r--r--spec/requests/api/repositories_spec.rb4
-rw-r--r--spec/requests/api/settings_spec.rb21
-rw-r--r--spec/routing/routing_spec.rb7
-rw-r--r--spec/rubocop/cop/qa/element_with_pattern_spec.rb50
-rw-r--r--spec/rubocop/qa_helpers_spec.rb37
-rw-r--r--spec/serializers/deployment_serializer_spec.rb35
-rw-r--r--spec/services/applications/create_service_spec.rb13
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb71
-rw-r--r--spec/services/clusters/applications/schedule_installation_service_spec.rb7
-rw-r--r--spec/services/clusters/create_service_spec.rb2
-rw-r--r--spec/services/clusters/update_service_spec.rb2
-rw-r--r--spec/services/projects/destroy_service_spec.rb23
-rw-r--r--spec/services/projects/import_service_spec.rb2
-rw-r--r--spec/support/features/issuable_quick_actions_shared_examples.rb9
-rw-r--r--spec/support/helpers/test_request_helpers.rb11
-rw-r--r--spec/support/shared_examples/dirty_submit_form_shared_examples.rb24
-rw-r--r--spec/support/shared_examples/models/cluster_application_core_shared_examples.rb56
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb83
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb17
-rw-r--r--spec/support/stored_repositories.rb19
-rwxr-xr-xvendor/languages.yml5488
-rw-r--r--yarn.lock29
816 files changed, 13611 insertions, 8387 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index d04a10a9127..fe0d5f5dce3 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -1,22 +1,13 @@
-env:
- browser: true
- es6: true
extends:
- - airbnb-base
- - prettier
- - plugin:vue/recommended
+ - '@gitlab'
globals:
__webpack_public_path__: true
gl: false
gon: false
localStorage: false
-parserOptions:
- parser: babel-eslint
plugins:
- - filenames
- import
- html
- - promise
settings:
html/html-extensions:
- '.html'
@@ -25,38 +16,12 @@ settings:
webpack:
config: './config/webpack.config.js'
rules:
- filenames/match-regex:
- - error
- - '^[a-z0-9_]+$'
import/no-commonjs: error
- promise/catch-or-return: error
- no-param-reassign:
- - error
- - props: true
- ignorePropertyModificationsFor:
- - 'acc' # for reduce accumulators
- - 'accumulator' # for reduce accumulators
- - 'el' # for DOM elements
- - 'element' # for DOM elements
- - 'state' # for Vuex mutations
no-underscore-dangle:
- error
- allow:
- __
- _links
- no-mixed-operators: off
- vue/html-self-closing:
- - error
- - html:
- void: always
- normal: never
- component: always
- svg: always
- math: always
- camelcase:
- - error
- - properties: never
- ignoreDestructuring: true
# Disabled for now, to make the airbnb-base 12.1.0 -> 13.1.0 update smoother
no-else-return:
- error
diff --git a/.rubocop.yml b/.rubocop.yml
index b7aec5b8b14..0f4018326a1 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -3,7 +3,9 @@ inherit_gem:
- rubocop-default.yml
inherit_from: .rubocop_todo.yml
-require: ./rubocop/rubocop
+require:
+ - ./rubocop/rubocop
+ - rubocop-rspec
AllCops:
TargetRailsVersion: 4.2
@@ -48,12 +50,20 @@ Style/FrozenStringLiteralComment:
- 'danger/**/*'
- 'db/**/*'
- 'ee/**/*'
- - 'lib/**/*'
+ - 'lib/gitlab/**/*'
+ - 'lib/tasks/**/*'
- 'qa/**/*'
- 'rubocop/**/*'
- 'scripts/**/*'
- 'spec/**/*'
+RSpec/FilePath:
+ Exclude:
+ - 'qa/**/*'
+ - 'spec/javascripts/fixtures/*'
+ - 'ee/spec/javascripts/fixtures/*'
+ - 'spec/requests/api/v3/*'
+
Naming/FileName:
ExpectMatchingDefinition: true
Exclude:
@@ -75,6 +85,7 @@ Naming/FileName:
- EE
- JSON
- LDAP
+ - SAML
- IO
- HMAC
- QA
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dcc2c01931d..43691af1877 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,14 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.3.5 (2018-10-15)
+
+### Fixed (2 changes)
+
+- Fix loading issue on some merge request discussion. !21982
+- Fix project deletion when there is a export available. !22276
+
+
## 11.3.3 (2018-10-04)
- No changes.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d2d385dff8f..b33ef79558e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -64,228 +64,56 @@ As of July 2018, all the documentation for contributing to the GitLab project ha
## Contribute to GitLab
-Thank you for your interest in contributing to GitLab. This guide details how
-to contribute to GitLab in a way that is easy for everyone.
-
-For a first-time step-by-step guide to the contribution process, please see
-["Contributing to GitLab"](https://about.gitlab.com/contributing/).
-
-Looking for something to work on? Look for issues in the [Backlog (Accepting merge requests) milestone](#i-want-to-contribute).
-
-GitLab comes in two flavors, GitLab Community Edition (CE) our free and open
-source edition, and GitLab Enterprise Edition (EE) which is our commercial
-edition. Throughout this guide you will see references to CE and EE for
-abbreviation.
-
-To get an overview of GitLab community membership including those that would be reviewing or merging your contributions, please visit [the community roles page](doc/development/contributing/community_roles.md).
-
-If you want to know how the GitLab [core team]
-operates please see [the GitLab contributing process](PROCESS.md).
-
-[GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
+This [documentation](doc/development/contributing/index.md#contribute-to-gitlab) has been moved.
## Security vulnerability disclosure
-Please report suspected security vulnerabilities in private to
-`support@gitlab.com`, also see the
-[disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/).
-Please do **NOT** create publicly viewable issues for suspected security
-vulnerabilities.
+This [documentation](doc/development/contributing/index.md#security-vulnerability-disclosure) has been moved.
## Code of Conduct
-### Our Pledge
-
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, sex characteristics, gender identity and expression,
-level of experience, education, socio-economic status, nationality, personal
-appearance, race, religion, or sexual identity and orientation.
-
-### Our Standards
-
-Examples of behavior that contributes to creating a positive environment
-include:
-
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
-
-* The use of sexualized language or imagery and unwelcome sexual attention or
- advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
- professional setting
-
-### Our Responsibilities
-
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
-
-Project maintainers have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct, or to ban temporarily or
-permanently any contributor for other behaviors that they deem inappropriate,
-threatening, offensive, or harmful.
-
-### Scope
-
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
-
-### Enforcement
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at conduct@gitlab.com. All
-complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The project team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
-
-Project maintainers who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by other
-members of the project's leadership.
-
-### Attribution
-
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
-available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
-
-[homepage]: https://www.contributor-covenant.org
+This [documentation](doc/development/contributing/index.md#code-of-conduct) has been moved.
## Closing policy for issues and merge requests
-GitLab is a popular open source project and the capacity to deal with issues
-and merge requests is limited. Out of respect for our volunteers, issues and
-merge requests not in line with the guidelines listed in this document may be
-closed without notice.
-
-Please treat our volunteers with courtesy and respect, it will go a long way
-towards getting your issue resolved.
-
-Issues and merge requests should be in English and contain appropriate language
-for audiences of all ages.
-
-If a contributor is no longer actively working on a submitted merge request
-we can decide that the merge request will be finished by one of our
-[Merge request coaches][team] or close the merge request. We make this decision
-based on how important the change is for our product vision. If a Merge request
-coach is going to finish the merge request we assign the
-~"coach will finish" label.
+This [documentation](doc/development/contributing/index.md#closing-policy-for-issues-and-merge-requests) has been moved.
## Helping others
-Please help other GitLab users when you can.
-The methods people will use to seek help can be found on the [getting help page][getting-help].
-
-Sign up for the mailing list, answer GitLab questions on StackOverflow or
-respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
-the remaining issues on the GitHub issue tracker.
+This [documentation](doc/development/contributing/index.md#helping-others) has been moved.
## I want to contribute!
-If you want to contribute to GitLab, [issues in the Backlog (Accepting merge requests)](https://gitlab.com/gitlab-org/gitlab-ce/issues?scope=all&utf8=✓&state=opened&assignee_id=0&milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;)
-are a great place to start. Issues with a lower weight (1 or 2) are deemed
-suitable for beginners. These issues will be of reasonable size and challenge,
-for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
-learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
-please consider we favor
-[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
+This [documentation](doc/development/contributing/index.md#i-want-to-contribute) has been moved.
## Contribution Flow
-When contributing to GitLab, your merge request is subject to review by merge request maintainers of a particular specialty.
-
-When you submit code to GitLab, we really want it to get merged, but there will be times when it will not be merged.
-
-When maintainers are reading through a merge request they may request guidance from other maintainers. If merge request maintainers conclude that the code should not be merged, our reasons will be fully disclosed. If it has been decided that the code quality is not up to GitLab’s standards, the merge request maintainer will refer the author to our docs and code style guides, and provide some guidance.
-
-Sometimes style guides will be followed but the code will lack structural integrity, or the maintainer will have reservations about the code’s overall quality. When there is a reservation the maintainer will inform the author and provide some guidance. The author may then choose to update the merge request. Once the merge request has been updated and reassigned to the maintainer, they will review the code again. Once the code has been resubmitted any number of times, the maintainer may choose to close the merge request with a summary of why it will not be merged, as well as some guidance. If the merge request is closed the maintainer will be open to discussion as to how to improve the code so it can be approved in the future.
-
-GitLab will do its best to review community contributions as quickly as possible. Specially appointed developers review community contributions daily. You may take a look at the [team page](https://about.gitlab.com/team/) for the merge request coach who specializes in the type of code you have written and mention them in the merge request. For example, if you have written some JavaScript in your code then you should mention the frontend merge request coach. If your code has multiple disciplines you may mention multiple merge request coaches.
-
-GitLab receives a lot of community contributions, so if your code has not been reviewed within 4 days of its initial submission feel free to re-mention the appropriate merge request coach.
-
-When submitting code to GitLab, you may feel that your contribution requires the aid of an external library. If your code includes an external library please provide a link to the library, as well as reasons for including it.
-
-When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
-
-[core team]: https://about.gitlab.com/core-team/
-[team]: https://about.gitlab.com/team/
-[getting-help]: https://about.gitlab.com/getting-help/
-[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
-[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?assignee_id=0&label_name[]=Accepting%20Merge%20Requests&sort=weight_asc
-[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
-[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
-[google-group]: https://groups.google.com/forum/#!forum/gitlabhq
-[stackoverflow]: https://stackoverflow.com/questions/tagged/gitlab
-[fpl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature+proposal
-[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests
-[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests
-[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
-[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
-[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
-[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
-[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
-[contributor-covenant]: http://contributor-covenant.org
-[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
-[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
-[changelog]: doc/development/changelog.md "Generate a changelog entry"
-[doc-guidelines]: doc/development/documentation/index.md "Documentation guidelines"
-[js-styleguide]: doc/development/fe_guide/style_guide_js.md "JavaScript styleguide"
-[scss-styleguide]: doc/development/fe_guide/style_guide_scss.md "SCSS styleguide"
-[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
-[UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/
-[license-finder-doc]: doc/development/licensing.md
-[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
-[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
-[testing]: doc/development/testing_guide/index.md
-[us-english]: https://en.wikipedia.org/wiki/American_English
-
+This [documentation](doc/development/contributing/index.md) has been moved.
## Workflow labels
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Type labels
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Subject labels
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Team labels
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Release Scoping labels
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Priority labels
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Severity labels
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
@@ -294,17 +122,14 @@ This [documentation](doc/development/contributing/issue_workflow.md) has been mo
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Label for community contributors
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
## Implement design & UI elements
This [documentation](doc/development/contributing/design.md) has been moved.
-
## Issue tracker
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
@@ -313,7 +138,6 @@ This [documentation](doc/development/contributing/issue_workflow.md) has been mo
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Feature proposals
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
@@ -322,32 +146,26 @@ This [documentation](doc/development/contributing/issue_workflow.md) has been mo
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Issue weight
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Regression issues
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Technical and UX debt
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
### Stewardship
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
-
## Merge requests
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
-
### Merge request guidelines
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
@@ -357,12 +175,10 @@ This [documentation](doc/development/contributing/merge_request_workflow.md) has
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
-
## Definition of done
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
-
## Style guides
This [documentation](doc/development/contributing/design.md) has been moved.
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index b1fa68e5df9..33e061fe7a0 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.125.0
+0.125.1
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 26aaba0e866..6085e946503 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.2.0
+1.2.1
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index d127a0ff9f1..9da0a092a0d 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-8.3.3
+8.4.0 \ No newline at end of file
diff --git a/Gemfile b/Gemfile
index ecbfba0827d..4b92bdb87fe 100644
--- a/Gemfile
+++ b/Gemfile
@@ -83,9 +83,6 @@ gem 'net-ldap'
# Only used to compute wiki page slugs
gem 'gitlab-gollum-lib', '~> 4.2', require: false
-# Language detection
-gem 'github-linguist', '~> 5.3.3', require: 'linguist'
-
# API
gem 'grape', '~> 1.1'
gem 'grape-entity', '~> 0.7.1'
@@ -146,6 +143,7 @@ gem 'rouge', '~> 3.1'
gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.2'
+gem 'escape_utils', '~> 1.1'
# Calendar rendering
gem 'icalendar'
@@ -421,7 +419,7 @@ end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.118.1', require: 'gitaly'
-gem 'grpc', '~> 1.11.0'
+gem 'grpc', '~> 1.15.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
gem 'google-protobuf', '= 3.5.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index f995d92abf0..8317f980eec 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -277,11 +277,6 @@ GEM
gitaly-proto (0.118.1)
google-protobuf (~> 3.1)
grpc (~> 1.10)
- github-linguist (5.3.3)
- charlock_holmes (~> 0.7.5)
- escape_utils (~> 1.1.0)
- mime-types (>= 1.19)
- rugged (>= 0.25.1)
github-markup (1.7.0)
gitlab-flowdock-git-hook (1.0.1)
flowdock (~> 0.7)
@@ -328,15 +323,14 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
google-protobuf (3.5.1)
- googleapis-common-protos-types (1.0.1)
+ googleapis-common-protos-types (1.0.2)
google-protobuf (~> 3.0)
- googleauth (0.6.2)
+ googleauth (0.6.6)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
- logging (~> 2.0)
memoist (~> 0.12)
multi_json (~> 1.11)
- os (~> 0.9)
+ os (>= 0.9, < 2.0)
signet (~> 0.7)
gpgme (2.0.13)
mini_portile2 (~> 2.1)
@@ -360,10 +354,9 @@ GEM
railties
sprockets-rails
graphql (1.8.1)
- grpc (1.11.0)
+ grpc (1.15.0)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
- googleauth (>= 0.5.1, < 0.7)
haml (5.0.4)
temple (>= 0.8.0)
tilt
@@ -465,11 +458,7 @@ GEM
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
- little-plugger (1.1.4)
locale (2.1.2)
- logging (2.2.2)
- little-plugger (~> 1.1)
- multi_json (~> 1.10)
lograge (0.10.0)
actionpack (>= 4)
activesupport (>= 4)
@@ -575,7 +564,7 @@ GEM
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
- os (0.9.6)
+ os (1.0.0)
parallel (1.12.1)
parser (2.5.1.0)
ast (~> 2.4.0)
@@ -843,7 +832,7 @@ GEM
sidekiq-cron (0.6.0)
rufus-scheduler (>= 3.3.0)
sidekiq (>= 4.2.1)
- signet (0.8.1)
+ signet (0.11.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
@@ -1006,6 +995,7 @@ DEPENDENCIES
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0)
+ escape_utils (~> 1.1)
factory_bot_rails (~> 4.8.2)
faraday (~> 0.12)
fast_blank
@@ -1028,7 +1018,6 @@ DEPENDENCIES
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1)
- github-linguist (~> 5.3.3)
github-markup (~> 1.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
@@ -1046,7 +1035,7 @@ DEPENDENCIES
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
graphql (~> 1.8.0)
- grpc (~> 1.11.0)
+ grpc (~> 1.15.0)
haml_lint (~> 0.26.0)
hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
@@ -1187,4 +1176,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.16.4
+ 1.16.6
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 52f9b0ccd55..36bbf403f01 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -280,11 +280,6 @@ GEM
gitaly-proto (0.118.1)
google-protobuf (~> 3.1)
grpc (~> 1.10)
- github-linguist (5.3.3)
- charlock_holmes (~> 0.7.5)
- escape_utils (~> 1.1.0)
- mime-types (>= 1.19)
- rugged (>= 0.25.1)
github-markup (1.7.0)
gitlab-flowdock-git-hook (1.0.1)
flowdock (~> 0.7)
@@ -331,15 +326,14 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
google-protobuf (3.5.1)
- googleapis-common-protos-types (1.0.1)
+ googleapis-common-protos-types (1.0.2)
google-protobuf (~> 3.0)
- googleauth (0.6.2)
+ googleauth (0.6.6)
faraday (~> 0.12)
jwt (>= 1.4, < 3.0)
- logging (~> 2.0)
memoist (~> 0.12)
multi_json (~> 1.11)
- os (~> 0.9)
+ os (>= 0.9, < 2.0)
signet (~> 0.7)
gpgme (2.0.13)
mini_portile2 (~> 2.1)
@@ -363,10 +357,9 @@ GEM
railties
sprockets-rails
graphql (1.8.1)
- grpc (1.11.0)
+ grpc (1.15.0)
google-protobuf (~> 3.1)
googleapis-common-protos-types (~> 1.0.0)
- googleauth (>= 0.5.1, < 0.7)
haml (5.0.4)
temple (>= 0.8.0)
tilt
@@ -468,11 +461,7 @@ GEM
xml-simple
licensee (8.9.2)
rugged (~> 0.24)
- little-plugger (1.1.4)
locale (2.1.2)
- logging (2.2.2)
- little-plugger (~> 1.1)
- multi_json (~> 1.10)
lograge (0.10.0)
actionpack (>= 4)
activesupport (>= 4)
@@ -579,7 +568,7 @@ GEM
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
- os (0.9.6)
+ os (1.0.0)
parallel (1.12.1)
parser (2.5.1.0)
ast (~> 2.4.0)
@@ -851,7 +840,7 @@ GEM
sidekiq-cron (0.6.0)
rufus-scheduler (>= 3.3.0)
sidekiq (>= 4.2.1)
- signet (0.8.1)
+ signet (0.11.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
@@ -1015,6 +1004,7 @@ DEPENDENCIES
ed25519 (~> 1.2)
email_reply_trimmer (~> 0.1)
email_spec (~> 2.2.0)
+ escape_utils (~> 1.1)
factory_bot_rails (~> 4.8.2)
faraday (~> 0.12)
fast_blank
@@ -1037,7 +1027,6 @@ DEPENDENCIES
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1)
- github-linguist (~> 5.3.3)
github-markup (~> 1.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
@@ -1055,7 +1044,7 @@ DEPENDENCIES
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
graphql (~> 1.8.0)
- grpc (~> 1.11.0)
+ grpc (~> 1.15.0)
haml_lint (~> 0.26.0)
hamlit (~> 2.8.8)
hangouts-chat (~> 0.0.5)
@@ -1196,4 +1185,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.16.4
+ 1.16.6
diff --git a/PROCESS.md b/PROCESS.md
index 38ec01f9de0..5fc2c4cf1df 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -208,6 +208,7 @@ the stable branch are:
* Fixes or improvements to automated QA scenarios
* [Documentation updates](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late) for changes in the same release
* New or updated translations (as long as they do not touch application code)
+* Changes that are behind a feature flag and have the ~"feature flag" label
During the feature freeze all merge requests that are meant to go into the
upcoming release should have the correct milestone assigned _and_ the
diff --git a/app/assets/images/koding-logo.svg b/app/assets/images/koding-logo.svg
deleted file mode 100644
index ad89d684d94..00000000000
--- a/app/assets/images/koding-logo.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14">
- <g fill="#d6d7d9">
- <path d="M8.7 0L5.3.3l3.2 6.8-3.2 6.6 3.5.3L12 6.9z"/>
- <ellipse cx="1.7" cy="11.1" rx="1.7" ry="1.7"/>
- <ellipse cx="1.7" cy="5.6" rx="1.7" ry="1.7"/>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 4e4598870fa..3cc89ff1955 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -13,11 +13,11 @@ export default () => {
if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relativeUrlRoot');
const assetsPath = editBlobForm.data('assetsPrefix');
- const blobLanguage = editBlobForm.data('blobLanguage');
+ const filePath = editBlobForm.data('blobFilename')
const currentAction = $('.js-file-title').data('currentAction');
const projectId = editBlobForm.data('project-id');
- new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage, currentAction, projectId);
+ new EditBlob(`${urlRoot}${assetsPath}`, filePath, currentAction, projectId);
new NewCommitForm(editBlobForm);
}
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index ec2b130ab7d..6e19548eed2 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -5,6 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import TemplateSelectorMediator from '../blob/file_template_mediator';
+import getModeByFileExtension from '~/lib/utils/ace_utils';
export default class EditBlob {
constructor(assetsPath, aceMode, currentAction, projectId) {
@@ -14,9 +15,10 @@ export default class EditBlob {
this.initFileSelectors(currentAction, projectId);
}
- configureAceEditor(aceMode, assetsPath) {
+ configureAceEditor(filePath, assetsPath) {
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
+ ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor');
@@ -25,8 +27,8 @@ export default class EditBlob {
this.editor.focus();
- if (aceMode) {
- this.editor.getSession().setMode(`ace/mode/${aceMode}`);
+ if (filePath) {
+ this.editor.getSession().setMode(getModeByFileExtension(filePath));
}
}
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 1fc7a29f785..75477ebb3b3 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -4,20 +4,17 @@ import { n__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import AccessorUtilities from '../../lib/utils/accessor';
-import boardList from './board_list.vue';
import BoardBlankState from './board_blank_state.vue';
-import './board_delete';
+import BoardDelete from './board_delete';
+import BoardList from './board_list.vue';
+import boardsStore from '../stores/boards_store';
+import { getBoardSortableDefaultOptions, sortableEnd } from '../mixins/sortable_default_options';
-const Store = gl.issueBoards.BoardsStore;
-
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.Board = Vue.extend({
+export default Vue.extend({
components: {
- boardList,
- 'board-delete': gl.issueBoards.BoardDelete,
BoardBlankState,
+ BoardDelete,
+ BoardList,
Icon,
},
directives: {
@@ -47,8 +44,8 @@ gl.issueBoards.Board = Vue.extend({
},
data () {
return {
- detailIssue: Store.detail,
- filter: Store.filter,
+ detailIssue: boardsStore.detail,
+ filter: boardsStore.filter,
};
},
computed: {
@@ -70,20 +67,20 @@ gl.issueBoards.Board = Vue.extend({
}
},
mounted () {
- this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
+ this.sortableOptions = getBoardSortableDefaultOptions({
disabled: this.disabled,
group: 'boards',
draggable: '.is-draggable',
handle: '.js-board-handle',
onEnd: (e) => {
- gl.issueBoards.onEnd();
+ sortableEnd();
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
const order = this.sortable.toArray();
- const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
+ const list = boardsStore.findList('id', parseInt(e.item.dataset.id, 10));
this.$nextTick(() => {
- Store.moveList(list, order);
+ boardsStore.moveList(list, order);
});
}
}
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index cde22725a89..38aaec73d7d 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -2,8 +2,7 @@
/* global ListLabel */
import _ from 'underscore';
import Cookies from 'js-cookie';
-
-const Store = gl.issueBoards.BoardsStore;
+import boardsStore from '../stores/boards_store';
export default {
data() {
@@ -19,7 +18,7 @@ export default {
this.clearBlankState();
this.predefinedLabels.forEach((label, i) => {
- Store.addList({
+ boardsStore.addList({
title: label.title,
position: i,
list_type: 'label',
@@ -30,14 +29,14 @@ export default {
});
});
- Store.state.lists = _.sortBy(Store.state.lists, 'position');
+ boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
// Save the labels
gl.boardService.generateDefaultLists()
.then(res => res.data)
.then((data) => {
data.forEach((listObj) => {
- const list = Store.findList('title', listObj.title);
+ const list = boardsStore.findList('title', listObj.title);
list.id = listObj.id;
list.label.id = listObj.label.id;
@@ -48,14 +47,14 @@ export default {
});
})
.catch(() => {
- Store.removeList(undefined, 'label');
+ boardsStore.removeList(undefined, 'label');
Cookies.remove('issue_board_welcome_hidden', {
path: '',
});
- Store.addBlankState();
+ boardsStore.addBlankState();
});
},
- clearBlankState: Store.removeBlankState.bind(Store),
+ clearBlankState: boardsStore.removeBlankState.bind(boardsStore),
},
};
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 0398102ad02..843498f0d06 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -2,8 +2,7 @@
/* eslint-disable vue/require-default-prop */
import IssueCardInner from './issue_card_inner.vue';
import eventHub from '../eventhub';
-
- const Store = gl.issueBoards.BoardsStore;
+ import boardsStore from '../stores/boards_store';
export default {
name: 'BoardsIssueCard',
@@ -42,7 +41,7 @@
data() {
return {
showDetail: false,
- detailIssue: Store.detail,
+ detailIssue: boardsStore.detail,
};
},
computed: {
@@ -63,11 +62,11 @@
if (this.showDetail) {
this.showDetail = false;
- if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
+ if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) {
eventHub.$emit('clearDetailIssue');
} else {
eventHub.$emit('newDetailIssue', this.issue);
- Store.detail.list = this.list;
+ boardsStore.detail.list = this.list;
}
}
},
diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js
index 240d0911a31..a5f9d65e4d5 100644
--- a/app/assets/javascripts/boards/components/board_delete.js
+++ b/app/assets/javascripts/boards/components/board_delete.js
@@ -1,12 +1,7 @@
-/* eslint-disable no-alert */
-
import $ from 'jquery';
import Vue from 'vue';
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.BoardDelete = Vue.extend({
+export default Vue.extend({
props: {
list: {
type: Object,
@@ -14,12 +9,13 @@ gl.issueBoards.BoardDelete = Vue.extend({
},
},
methods: {
- deleteBoard () {
+ deleteBoard() {
$(this.$el).tooltip('hide');
+ // eslint-disable-next-line no-alert
if (window.confirm('Are you sure you want to delete this list?')) {
this.list.destroy();
}
- }
- }
+ },
+ },
});
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 7ddb22ad824..4dc56c670f0 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -3,8 +3,8 @@ import Sortable from 'sortablejs';
import boardNewIssue from './board_new_issue.vue';
import boardCard from './board_card.vue';
import eventHub from '../eventhub';
-
-const Store = gl.issueBoards.BoardsStore;
+import boardsStore from '../stores/boards_store';
+import { getBoardSortableDefaultOptions, sortableStart } from '../mixins/sortable_default_options';
export default {
name: 'BoardList',
@@ -46,7 +46,7 @@ export default {
data() {
return {
scrollOffset: 250,
- filters: Store.state.filters,
+ filters: boardsStore.state.filters,
showCount: false,
showIssueForm: false,
};
@@ -61,13 +61,14 @@ export default {
},
issues() {
this.$nextTick(() => {
- if (this.scrollHeight() <= this.listHeight() &&
- this.list.issuesSize > this.list.issues.length) {
+ if (
+ this.scrollHeight() <= this.listHeight() &&
+ this.list.issuesSize > this.list.issues.length
+ ) {
this.list.page += 1;
- this.list.getIssues(false)
- .catch(() => {
- // TODO: handle request error
- });
+ this.list.getIssues(false).catch(() => {
+ // TODO: handle request error
+ });
}
if (this.scrollHeight() > Math.ceil(this.listHeight())) {
@@ -83,7 +84,7 @@ export default {
eventHub.$on(`scroll-board-list-${this.list.id}`, this.scrollToTop);
},
mounted() {
- const options = gl.issueBoards.getBoardSortableDefaultOptions({
+ const options = getBoardSortableDefaultOptions({
scroll: true,
disabled: this.disabled,
filter: '.board-list-count, .is-disabled',
@@ -108,7 +109,8 @@ export default {
// So from there, we can get reference to actual container
// and thus the container type to enable Copy or Move
if (e.target) {
- const containerEl = e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list');
+ const containerEl =
+ e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list');
const toBoardType = containerEl.dataset.boardType;
const cloneActions = {
label: ['milestone', 'assignee'],
@@ -120,8 +122,9 @@ export default {
const fromBoardType = this.list.type;
// For each list we check if the destination list is
// a the list were we should clone the issue
- const shouldClone = Object.entries(cloneActions).some(entry => (
- fromBoardType === entry[0] && entry[1].includes(toBoardType)));
+ const shouldClone = Object.entries(cloneActions).some(
+ entry => fromBoardType === entry[0] && entry[1].includes(toBoardType),
+ );
if (shouldClone) {
return 'clone';
@@ -133,28 +136,36 @@ export default {
},
revertClone: true,
},
- onStart: (e) => {
+ onStart: e => {
const card = this.$refs.issue[e.oldIndex];
card.showDetail = false;
- Store.moving.list = card.list;
- Store.moving.issue = Store.moving.list.findIssue(+e.item.dataset.issueId);
+ boardsStore.moving.list = card.list;
+ boardsStore.moving.issue = boardsStore.moving.list.findIssue(+e.item.dataset.issueId);
- gl.issueBoards.onStart();
+ sortableStart();
},
- onAdd: (e) => {
- gl.issueBoards.BoardsStore
- .moveIssueToList(Store.moving.list, this.list, Store.moving.issue, e.newIndex);
+ onAdd: e => {
+ boardsStore.moveIssueToList(
+ boardsStore.moving.list,
+ this.list,
+ boardsStore.moving.issue,
+ e.newIndex,
+ );
this.$nextTick(() => {
e.item.remove();
});
},
- onUpdate: (e) => {
- const sortedArray = this.sortable.toArray()
- .filter(id => id !== '-1');
- gl.issueBoards.BoardsStore
- .moveIssueInList(this.list, Store.moving.issue, e.oldIndex, e.newIndex, sortedArray);
+ onUpdate: e => {
+ const sortedArray = this.sortable.toArray().filter(id => id !== '-1');
+ boardsStore.moveIssueInList(
+ this.list,
+ boardsStore.moving.issue,
+ e.oldIndex,
+ e.newIndex,
+ sortedArray,
+ );
},
onMove(e) {
return !e.related.classList.contains('board-list-count');
@@ -192,16 +203,14 @@ export default {
if (getIssues) {
this.list.loadingMore = true;
- getIssues
- .then(loadingDone)
- .catch(loadingDone);
+ getIssues.then(loadingDone).catch(loadingDone);
}
},
toggleForm() {
this.showIssueForm = !this.showIssueForm;
},
onScroll() {
- if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
+ if (!this.list.loadingMore && this.scrollTop() > this.scrollHeight() - this.scrollOffset) {
this.loadNextPage();
}
},
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index f7ce5128964..030288a1c9d 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -4,8 +4,7 @@ import { Button } from '@gitlab-org/gitlab-ui';
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
import ListIssue from '../models/issue';
-
-const Store = gl.issueBoards.BoardsStore;
+import boardsStore from '../stores/boards_store';
export default {
name: 'BoardNewIssue',
@@ -68,8 +67,8 @@ export default {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
- Store.detail.issue = issue;
- Store.detail.list = this.list;
+ boardsStore.detail.issue = issue;
+ boardsStore.detail.list = this.list;
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index df7efd3fa5c..62666954de0 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -14,13 +14,9 @@ import IssuableContext from '../../issuable_context';
import LabelsSelect from '../../labels_select';
import Subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
import MilestoneSelect from '../../milestone_select';
+import boardsStore from '../stores/boards_store';
-const Store = gl.issueBoards.BoardsStore;
-
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.BoardSidebar = Vue.extend({
+export default Vue.extend({
components: {
AssigneeTitle,
Assignees,
@@ -35,7 +31,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
},
data() {
return {
- detail: Store.detail,
+ detail: boardsStore.detail,
issue: {},
list: {},
loadingAssignees: false,
@@ -117,18 +113,18 @@ gl.issueBoards.BoardSidebar = Vue.extend({
this.saveAssignees();
},
removeAssignee (a) {
- gl.issueBoards.BoardsStore.detail.issue.removeAssignee(a);
+ boardsStore.detail.issue.removeAssignee(a);
},
addAssignee (a) {
- gl.issueBoards.BoardsStore.detail.issue.addAssignee(a);
+ boardsStore.detail.issue.addAssignee(a);
},
removeAllAssignees () {
- gl.issueBoards.BoardsStore.detail.issue.removeAllAssignees();
+ boardsStore.detail.issue.removeAllAssignees();
},
saveAssignees () {
this.loadingAssignees = true;
- gl.issueBoards.BoardsStore.detail.issue.update()
+ boardsStore.detail.issue.update()
.then(() => {
this.loadingAssignees = false;
})
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index 8b5536200e1..aa98f35786e 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -1,14 +1,15 @@
<script>
import $ from 'jquery';
+ import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
import tooltip from '../../vue_shared/directives/tooltip';
-
- const Store = gl.issueBoards.BoardsStore;
+ import boardsStore from '../stores/boards_store';
export default {
components: {
UserAvatarLink,
+ Icon,
},
directives: {
tooltip,
@@ -110,7 +111,7 @@
filterByLabel(label, e) {
if (!this.updateFilters) return;
- const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
+ const filterPath = boardsStore.filter.path.split('&');
const labelTitle = encodeURIComponent(label.title);
const param = `label_name[]=${labelTitle}`;
const labelIndex = filterPath.indexOf(param);
@@ -122,9 +123,9 @@
filterPath.splice(labelIndex, 1);
}
- gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
+ boardsStore.filter.path = filterPath.join('&');
- Store.updateFiltersUrl();
+ boardsStore.updateFiltersUrl();
eventHub.$emit('updateTokens');
},
@@ -141,11 +142,11 @@
<div>
<div class="board-card-header">
<h4 class="board-card-title">
- <i
+ <icon
v-if="issue.confidential"
- class="fa fa-eye-slash confidential-icon"
- aria-hidden="true"
- ></i>
+ name="eye-slash"
+ class="confidential-icon"
+ />
<a
:href="issue.path"
:title="issue.title"
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index d4affc8c3de..268ca6bca13 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -5,6 +5,7 @@ import ListsDropdown from './lists_dropdown.vue';
import { pluralize } from '../../../lib/utils/text_utility';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
+import boardsStore from '../../stores/boards_store';
export default {
components: {
@@ -14,7 +15,7 @@ export default {
data() {
return {
modal: ModalStore.store,
- state: gl.issueBoards.BoardsStore.state,
+ state: boardsStore.state,
};
},
computed: {
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index a58b5afe970..c93fd9f415c 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -1,4 +1,5 @@
<script>
+ import Icon from '~/vue_shared/components/icon.vue';
import bp from '../../../breakpoints';
import ModalStore from '../../stores/modal_store';
import IssueCardInner from '../issue_card_inner.vue';
@@ -6,6 +7,7 @@
export default {
components: {
IssueCardInner,
+ Icon,
},
props: {
issueLinkBase: {
@@ -147,13 +149,13 @@
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath"/>
- <span
+ <icon
v-if="issue.selected"
:aria-label="'Issue #' + issue.id + ' selected'"
+ name="mobile-issue-close"
aria-checked="true"
- class="issue-card-selected text-center">
- <i class="fa fa-check"></i>
- </span>
+ class="issue-card-selected text-center"
+ />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
index 4f23e5db35c..3baac08d411 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
@@ -1,15 +1,18 @@
<script>
import { Link } from '@gitlab-org/gitlab-ui';
+import Icon from '~/vue_shared/components/icon.vue';
import ModalStore from '../../stores/modal_store';
+import boardsStore from '../../stores/boards_store';
export default {
components: {
'gl-link': Link,
+ Icon,
},
data() {
return {
modal: ModalStore.store,
- state: gl.issueBoards.BoardsStore.state,
+ state: boardsStore.state,
};
},
computed: {
@@ -34,7 +37,9 @@ export default {
class="dropdown-label-box">
</span>
{{ selected.title }}
- <i class="fa fa-chevron-down"></i>
+ <icon
+ name="chevron-down"
+ />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 448ab9ed135..2c2045f8901 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -4,16 +4,12 @@ import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
-
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-const Store = gl.issueBoards.BoardsStore;
+import boardsStore from '../stores/boards_store';
$(document).off('created.label').on('created.label', (e, label) => {
- Store.new({
+ boardsStore.new({
title: label.title,
- position: Store.state.lists.length - 2,
+ position: boardsStore.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
@@ -23,7 +19,7 @@ $(document).off('created.label').on('created.label', (e, label) => {
});
});
-gl.issueBoards.newListDropdownInit = () => {
+export default function initNewListDropdown() {
$('.js-new-board-list').each(function () {
const $this = $(this);
new CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespacePath'), $this.data('projectPath'));
@@ -36,7 +32,7 @@ gl.issueBoards.newListDropdownInit = () => {
});
},
renderRow (label) {
- const active = Store.findList('title', label.title);
+ const active = boardsStore.findList('title', label.title);
const $li = $('<li />');
const $a = $('<a />', {
class: (active ? `is-active js-board-list-${active.id}` : ''),
@@ -62,10 +58,10 @@ gl.issueBoards.newListDropdownInit = () => {
const label = options.selectedObj;
e.preventDefault();
- if (!Store.findList('title', label.title)) {
- Store.new({
+ if (!boardsStore.findList('title', label.title)) {
+ boardsStore.new({
title: label.title,
- position: Store.state.lists.length - 2,
+ position: boardsStore.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
@@ -74,9 +70,9 @@ gl.issueBoards.newListDropdownInit = () => {
},
});
- Store.state.lists = _.sortBy(Store.state.lists, 'position');
+ boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
}
},
});
});
-};
+}
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index d4676914e02..4e8fe16160a 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -1,11 +1,15 @@
<script>
import $ from 'jquery';
import _ from 'underscore';
+import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../eventhub';
import Api from '../../api';
export default {
name: 'BoardProjectSelect',
+ components: {
+ Icon,
+ },
props: {
groupId: {
type: Number,
@@ -78,11 +82,9 @@ export default {
aria-expanded="false"
>
{{ selectedProjectName }}
- <i
- class="fa fa-chevron-down"
- aria-hidden="true"
- >
- </i>
+ <icon
+ name="chevron-down"
+ />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
<div class="dropdown-title">
@@ -92,12 +94,11 @@ export default {
type="button"
class="dropdown-title-button dropdown-menu-close"
>
- <i
- aria-hidden="true"
+ <icon
+ name="merge-request-close-m"
data-hidden="true"
- class="fa fa-times dropdown-menu-close-icon"
- >
- </i>
+ class="dropdown-menu-close-icon"
+ />
</button>
</div>
<div class="dropdown-input">
@@ -106,12 +107,11 @@ export default {
type="search"
placeholder="Search projects"
/>
- <i
- aria-hidden="true"
+ <icon
+ name="search"
+ class="dropdown-input-search"
data-hidden="true"
- class="fa fa-search dropdown-input-search"
- >
- </i>
+ />
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading">
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
index 90d4c710daf..b8f2e324d43 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
@@ -2,8 +2,7 @@
import Vue from 'vue';
import Flash from '../../../flash';
import { __ } from '../../../locale';
-
- const Store = gl.issueBoards.BoardsStore;
+ import boardsStore from '../../stores/boards_store';
export default Vue.extend({
props: {
@@ -49,7 +48,7 @@
list.removeIssue(issue);
});
- Store.detail.issue = {};
+ boardsStore.detail.issue = {};
},
/**
* Build the default patch request.
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index 46d61ebbf24..acf41e5689e 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -1,5 +1,6 @@
import FilteredSearchContainer from '../filtered_search/container';
import FilteredSearchManager from '../filtered_search/filtered_search_manager';
+import boardsStore from './stores/boards_store';
export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
@@ -23,7 +24,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
this.store.path = path.substr(1);
if (this.updateUrl) {
- gl.issueBoards.BoardsStore.updateFiltersUrl();
+ boardsStore.updateFiltersUrl();
}
}
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index caa6ce84335..91861f2f9ee 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -14,24 +14,22 @@ import './models/issue';
import './models/list';
import './models/milestone';
import './models/project';
-import './stores/boards_store';
+import boardsStore from './stores/boards_store';
import ModalStore from './stores/modal_store';
import BoardService from './services/board_service';
import modalMixin from './mixins/modal_mixins';
-import './mixins/sortable_default_options';
import './filters/due_date_filters';
-import './components/board';
-import './components/board_sidebar';
-import './components/new_list_dropdown';
+import Board from './components/board';
+import BoardSidebar from './components/board_sidebar';
+import initNewListDropdown from './components/new_list_dropdown';
import BoardAddIssuesModal from './components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor';
import { NavigationType } from '~/lib/utils/common_utils';
+let issueBoardsApp;
+
export default () => {
const $boardApp = document.getElementById('board-app');
- const Store = gl.issueBoards.BoardsStore;
-
- window.gl = window.gl || {};
// check for browser back and trigger a hard reload to circumvent browser caching.
window.addEventListener('pageshow', (event) => {
@@ -43,25 +41,21 @@ export default () => {
}
});
- if (gl.IssueBoardsApp) {
- gl.IssueBoardsApp.$destroy(true);
+ if (issueBoardsApp) {
+ issueBoardsApp.$destroy(true);
}
- Store.create();
-
- // hack to allow sidebar scripts like milestone_select manipulate the BoardsStore
- gl.issueBoards.boardStoreIssueSet = (...args) => Vue.set(Store.detail.issue, ...args);
- gl.issueBoards.boardStoreIssueDelete = (...args) => Vue.delete(Store.detail.issue, ...args);
+ boardsStore.create();
- gl.IssueBoardsApp = new Vue({
+ issueBoardsApp = new Vue({
el: $boardApp,
components: {
- board: gl.issueBoards.Board,
- 'board-sidebar': gl.issueBoards.BoardSidebar,
+ Board,
+ BoardSidebar,
BoardAddIssuesModal,
},
data: {
- state: Store.state,
+ state: boardsStore.state,
loading: true,
boardsEndpoint: $boardApp.dataset.boardsEndpoint,
listsEndpoint: $boardApp.dataset.listsEndpoint,
@@ -70,7 +64,7 @@ export default () => {
issueLinkBase: $boardApp.dataset.issueLinkBase,
rootPath: $boardApp.dataset.rootPath,
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
- detailIssue: Store.detail,
+ detailIssue: boardsStore.detail,
defaultAvatar: $boardApp.dataset.defaultAvatar,
},
computed: {
@@ -85,7 +79,7 @@ export default () => {
bulkUpdatePath: this.bulkUpdatePath,
boardId: this.boardId,
});
- Store.rootPath = this.boardsEndpoint;
+ boardsStore.rootPath = this.boardsEndpoint;
eventHub.$on('updateTokens', this.updateTokens);
eventHub.$on('newDetailIssue', this.updateDetailIssue);
@@ -99,16 +93,16 @@ export default () => {
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
},
mounted() {
- this.filterManager = new FilteredSearchBoards(Store.filter, true, Store.cantEdit);
+ this.filterManager = new FilteredSearchBoards(boardsStore.filter, true, boardsStore.cantEdit);
this.filterManager.setup();
- Store.disabled = this.disabled;
+ boardsStore.disabled = this.disabled;
gl.boardService
.all()
.then(res => res.data)
.then(data => {
data.forEach(board => {
- const list = Store.addList(board, this.defaultAvatar);
+ const list = boardsStore.addList(board, this.defaultAvatar);
if (list.type === 'closed') {
list.position = Infinity;
@@ -119,7 +113,7 @@ export default () => {
this.state.lists = _.sortBy(this.state.lists, 'position');
- Store.addBlankState();
+ boardsStore.addBlankState();
this.loading = false;
})
.catch(() => {
@@ -148,13 +142,13 @@ export default () => {
});
}
- Store.detail.issue = newIssue;
+ boardsStore.detail.issue = newIssue;
},
clearDetailIssue() {
- Store.detail.issue = {};
+ boardsStore.detail.issue = {};
},
toggleSubscription(id) {
- const { issue } = Store.detail;
+ const { issue } = boardsStore.detail;
if (issue.id === id && issue.toggleSubscriptionEndpoint) {
issue.setFetchingState('subscriptions', true);
BoardService.toggleIssueSubscription(issue.toggleSubscriptionEndpoint)
@@ -173,26 +167,28 @@ export default () => {
},
});
- gl.IssueBoardsSearch = new Vue({
+ // eslint-disable-next-line no-new
+ new Vue({
el: document.getElementById('js-add-list'),
data: {
- filters: Store.state.filters,
+ filters: boardsStore.state.filters,
},
mounted() {
- gl.issueBoards.newListDropdownInit();
+ initNewListDropdown();
},
});
const issueBoardsModal = document.getElementById('js-add-issues-btn');
if (issueBoardsModal) {
- gl.IssueBoardsModalAddBtn = new Vue({
+ // eslint-disable-next-line no-new
+ new Vue({
el: issueBoardsModal,
mixins: [modalMixin],
data() {
return {
modal: ModalStore.store,
- store: Store.state,
+ store: boardsStore.state,
canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
};
},
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js
index a8df45fc473..c9cde4effb9 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js
@@ -3,32 +3,29 @@
import $ from 'jquery';
import sortableConfig from '../../sortable/sortable_config';
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.onStart = () => {
+export function sortableStart() {
$('.has-tooltip').tooltip('hide')
.tooltip('disable');
document.body.classList.add('is-dragging');
-};
+}
-gl.issueBoards.onEnd = () => {
+export function sortableEnd() {
$('.has-tooltip').tooltip('enable');
document.body.classList.remove('is-dragging');
-};
+}
-gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
+export function getBoardSortableDefaultOptions(obj) {
+ const touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
-gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
const defaultSortOptions = Object.assign({}, sortableConfig, {
filter: '.board-delete, .btn',
- delay: gl.issueBoards.touchEnabled ? 100 : 0,
- scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
+ delay: touchEnabled ? 100 : 0,
+ scrollSensitivity: touchEnabled ? 60 : 100,
scrollSpeed: 20,
- onStart: gl.issueBoards.onStart,
- onEnd: gl.issueBoards.onEnd,
+ onStart: sortableStart,
+ onEnd: sortableEnd,
});
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
return defaultSortOptions;
-};
+}
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 609659bdf93..52d04389b88 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -6,6 +6,7 @@
import Vue from 'vue';
import '~/vue_shared/models/label';
import IssueProject from './project';
+import boardsStore from '../stores/boards_store';
class ListIssue {
constructor (obj, defaultAvatar) {
@@ -86,7 +87,7 @@ class ListIssue {
}
getLists () {
- return gl.issueBoards.BoardsStore.state.lists.filter(list => list.findIssue(this.id));
+ return boardsStore.state.lists.filter(list => list.findIssue(this.id));
}
updateData(newData) {
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 58e423fbd44..3161f1da8c9 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -5,6 +5,7 @@ import { __ } from '~/locale';
import ListLabel from '~/vue_shared/models/label';
import ListAssignee from '~/vue_shared/models/assignee';
import { urlParamsToObject } from '~/lib/utils/common_utils';
+import boardsStore from '../stores/boards_store';
const PER_PAGE = 20;
@@ -89,9 +90,9 @@ class List {
}
destroy() {
- const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this);
- gl.issueBoards.BoardsStore.state.lists.splice(index, 1);
- gl.issueBoards.BoardsStore.updateNewListDropdown(this.id);
+ const index = boardsStore.state.lists.indexOf(this);
+ boardsStore.state.lists.splice(index, 1);
+ boardsStore.updateNewListDropdown(this.id);
gl.boardService.destroyList(this.id).catch(() => {
// TODO: handle request error
@@ -116,7 +117,7 @@ class List {
getIssues(emptyIssues = true) {
const data = {
- ...urlParamsToObject(gl.issueBoards.BoardsStore.filter.path),
+ ...urlParamsToObject(boardsStore.filter.path),
page: this.page,
};
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index bd181807e1f..471955747fd 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -3,13 +3,11 @@
import $ from 'jquery';
import _ from 'underscore';
+import Vue from 'vue';
import Cookies from 'js-cookie';
import { getUrlParamsArray } from '~/lib/utils/common_utils';
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.BoardsStore = {
+const boardsStore = {
disabled: false,
filter: {
path: '',
@@ -167,3 +165,16 @@ gl.issueBoards.BoardsStore = {
window.history.pushState(null, null, `?${this.filter.path}`);
}
};
+
+// hacks added in order to allow milestone_select to function properly
+// TODO: remove these
+
+export function boardStoreIssueSet(...args) {
+ Vue.set(boardsStore.detail.issue, ...args);
+}
+
+export function boardStoreIssueDelete(...args) {
+ Vue.delete(boardsStore.detail.issue, ...args);
+}
+
+export default boardsStore;
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 65e7cee7039..ebf76af5966 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -1,6 +1,6 @@
import Visibility from 'visibilityjs';
import Vue from 'vue';
-import PersistentUserCallout from '../persistent_user_callout';
+import initDismissableCallout from '~/dismissable_callout';
import { s__, sprintf } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
@@ -62,7 +62,7 @@ export default class Clusters {
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
- Clusters.initDismissableCallout();
+ initDismissableCallout('.js-cluster-security-warning');
initSettingsPanels();
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
this.initApplications();
@@ -105,12 +105,6 @@ export default class Clusters {
});
}
- static initDismissableCallout() {
- const callout = document.querySelector('.js-cluster-security-warning');
-
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
- }
-
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js
index e32d507d1f7..789c8360124 100644
--- a/app/assets/javascripts/clusters/clusters_index.js
+++ b/app/assets/javascripts/clusters/clusters_index.js
@@ -1,15 +1,14 @@
import createFlash from '~/flash';
import { __ } from '~/locale';
import setupToggleButtons from '~/toggle_buttons';
-import PersistentUserCallout from '../persistent_user_callout';
+import initDismissableCallout from '~/dismissable_callout';
import ClustersService from './services/clusters_service';
export default () => {
const clusterList = document.querySelector('.js-clusters-list');
- const callout = document.querySelector('.gcp-signup-offer');
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ initDismissableCallout('.gcp-signup-offer');
// The empty state won't have a clusterList
if (clusterList) {
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index a1069985178..6e7b5eb5526 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -1,6 +1,6 @@
<script>
import _ from 'underscore';
-import helmInstallIllustration from '@gitlab-org/gitlab-svgs/illustrations/kubernetes-installation.svg';
+import helmInstallIllustration from '@gitlab-org/gitlab-svgs/dist/illustrations/kubernetes-installation.svg';
import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png';
import gitlabLogo from 'images/cluster_app_logos/gitlab.png';
import helmLogo from 'images/cluster_app_logos/helm.png';
diff --git a/app/assets/javascripts/cycle_analytics/components/banner.vue b/app/assets/javascripts/cycle_analytics/components/banner.vue
index 410d4873e55..88570160f26 100644
--- a/app/assets/javascripts/cycle_analytics/components/banner.vue
+++ b/app/assets/javascripts/cycle_analytics/components/banner.vue
@@ -1,7 +1,11 @@
<script>
+ import Icon from '~/vue_shared/components/icon.vue';
import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg';
export default {
+ components: {
+ Icon,
+ },
props: {
documentationLink: {
type: String,
@@ -28,10 +32,9 @@
type="button"
@click="dismissOverviewDialog"
>
- <i
- class="fa fa-times"
- aria-hidden="true">
- </i>
+ <icon
+ name="close"
+ />
</button>
<div
class="svg-container"
diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
index c3acc352d5e..f4b333f3700 100644
--- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
@@ -115,7 +115,7 @@ export default {
<span>
{{ selectedVersionName }}
</span>
- <Icon
+ <icon
:size="12"
name="angle-down"
class="position-absolute"
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 15b37243030..dcf1057eb84 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -20,6 +20,11 @@ export default {
Tooltip,
},
props: {
+ discussionPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
diffFile: {
type: Object,
required: true,
@@ -65,8 +70,7 @@ export default {
if (this.diffFile.submodule) {
return this.diffFile.submoduleTreeUrl || this.diffFile.submoduleLink;
}
-
- return `#${this.diffFile.fileHash}`;
+ return this.discussionPath;
},
filePath() {
if (this.diffFile.submodule) {
@@ -152,7 +156,7 @@ export default {
v-once
ref="titleWrapper"
:href="titleLink"
- class="append-right-4"
+ class="append-right-4 js-title-wrapper"
>
<file-icon
:file-name="filePath"
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index ae8930c8968..1c5c35071de 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -1,5 +1,6 @@
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
+import bp from '~/breakpoints';
import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants';
const viewTypeFromQueryString = getParameterValues('view')[0];
@@ -20,6 +21,7 @@ export default () => ({
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
tree: [],
treeEntries: {},
- showTreeList: storedTreeShow === null ? true : storedTreeShow === 'true',
+ showTreeList:
+ storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : storedTreeShow === 'true',
currentDiffFileId: '',
});
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_collection.js b/app/assets/javascripts/dirty_submit/dirty_submit_collection.js
new file mode 100644
index 00000000000..42b051b2270
--- /dev/null
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_collection.js
@@ -0,0 +1,13 @@
+import DirtySubmitForm from './dirty_submit_form';
+
+class DirtySubmitCollection {
+ constructor(forms) {
+ this.forms = forms;
+
+ this.dirtySubmits = [];
+
+ this.forms.forEach(form => this.dirtySubmits.push(new DirtySubmitForm(form)));
+ }
+}
+
+export default DirtySubmitCollection;
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_factory.js b/app/assets/javascripts/dirty_submit/dirty_submit_factory.js
new file mode 100644
index 00000000000..95a896a7f0b
--- /dev/null
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_factory.js
@@ -0,0 +1,9 @@
+import DirtySubmitCollection from './dirty_submit_collection';
+import DirtySubmitForm from './dirty_submit_form';
+
+export default function dirtySubmitFactory(formOrForms) {
+ const isCollection = formOrForms instanceof NodeList || formOrForms instanceof Array;
+ const DirtySubmitClass = isCollection ? DirtySubmitCollection : DirtySubmitForm;
+
+ return new DirtySubmitClass(formOrForms);
+}
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_form.js b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
new file mode 100644
index 00000000000..5bea47f23c5
--- /dev/null
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
@@ -0,0 +1,82 @@
+import _ from 'underscore';
+
+class DirtySubmitForm {
+ constructor(form) {
+ this.form = form;
+ this.dirtyInputs = [];
+ this.isDisabled = true;
+
+ this.init();
+ }
+
+ init() {
+ this.inputs = this.form.querySelectorAll('input, textarea, select');
+ this.submits = this.form.querySelectorAll('input[type=submit], button[type=submit]');
+
+ this.inputs.forEach(DirtySubmitForm.initInput);
+ this.toggleSubmission();
+
+ this.registerListeners();
+ }
+
+ registerListeners() {
+ const throttledUpdateDirtyInput = _.throttle(
+ event => this.updateDirtyInput(event),
+ DirtySubmitForm.THROTTLE_DURATION,
+ );
+ this.form.addEventListener('input', throttledUpdateDirtyInput);
+ this.form.addEventListener('submit', event => this.formSubmit(event));
+ }
+
+ updateDirtyInput(event) {
+ const input = event.target;
+
+ if (!input.dataset.dirtySubmitOriginalValue) return;
+
+ this.updateDirtyInputs(input);
+ this.toggleSubmission();
+ }
+
+ updateDirtyInputs(input) {
+ const { name } = input;
+ const isDirty =
+ input.dataset.dirtySubmitOriginalValue !== DirtySubmitForm.inputCurrentValue(input);
+ const indexOfInputName = this.dirtyInputs.indexOf(name);
+ const isExisting = indexOfInputName !== -1;
+
+ if (isDirty && !isExisting) this.dirtyInputs.push(name);
+ if (!isDirty && isExisting) this.dirtyInputs.splice(indexOfInputName, 1);
+ }
+
+ toggleSubmission() {
+ this.isDisabled = this.dirtyInputs.length === 0;
+ this.submits.forEach(element => {
+ element.disabled = this.isDisabled;
+ });
+ }
+
+ formSubmit(event) {
+ if (this.isDisabled) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ }
+
+ return !this.isDisabled;
+ }
+
+ static initInput(element) {
+ element.dataset.dirtySubmitOriginalValue = DirtySubmitForm.inputCurrentValue(element);
+ }
+
+ static isInputCheckable(input) {
+ return input.type === 'checkbox' || input.type === 'radio';
+ }
+
+ static inputCurrentValue(input) {
+ return DirtySubmitForm.isInputCheckable(input) ? input.checked.toString() : input.value;
+ }
+}
+
+DirtySubmitForm.THROTTLE_DURATION = 500;
+
+export default DirtySubmitForm;
diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js
new file mode 100644
index 00000000000..5185b019376
--- /dev/null
+++ b/app/assets/javascripts/dismissable_callout.js
@@ -0,0 +1,27 @@
+import $ from 'jquery';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+
+export default function initDismissableCallout(alertSelector) {
+ const alertEl = document.querySelector(alertSelector);
+ if (!alertEl) {
+ return;
+ }
+
+ const closeButtonEl = alertEl.getElementsByClassName('close')[0];
+ const { dismissEndpoint, featureId } = closeButtonEl.dataset;
+
+ closeButtonEl.addEventListener('click', () => {
+ axios
+ .post(dismissEndpoint, {
+ feature_name: featureId,
+ })
+ .then(() => {
+ $(alertEl).alert('close');
+ })
+ .catch(() => {
+ Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
+ });
+ });
+}
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index 8abd8bc581a..c7b5a35cc14 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -5,6 +5,7 @@ import { __ } from '~/locale';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
+import boardsStore from './boards/stores/boards_store';
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) {
@@ -58,7 +59,7 @@ class DueDateSelect {
$dueDateInput.val(calendar.toString(dateText));
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
- gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
+ boardsStore.detail.issue.dueDate = $dueDateInput.val();
this.updateIssueBoardIssue();
} else {
this.saveDueDate(true);
@@ -79,7 +80,7 @@ class DueDateSelect {
calendar.setDate(null);
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
- gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
+ boardsStore.detail.issue.dueDate = '';
this.updateIssueBoardIssue();
} else {
$(`input[name='${this.fieldName}']`).val('');
@@ -123,7 +124,7 @@ class DueDateSelect {
this.$loading.fadeOut();
};
- gl.issueBoards.BoardsStore.detail.issue
+ boardsStore.detail.issue
.update(this.$dropdown.attr('data-issue-update'))
.then(fadeOutLoader)
.catch(fadeOutLoader);
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index e1f9248bc4c..2bc168a6b02 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -60,11 +60,9 @@ export default {
>
<span>
<icon name="play" />
- <i
- class="fa fa-caret-down"
- aria-hidden="true"
- >
- </i>
+ <icon
+ name="chevron-down"
+ />
<gl-loading-icon v-if="isLoading" />
</span>
</button>
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index ad5d16874f3..bb9c139727e 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -4,6 +4,7 @@ import _ from 'underscore';
import tooltip from '~/vue_shared/directives/tooltip';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '~/lib/utils/text_utility';
+import Icon from '~/vue_shared/components/icon.vue';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
@@ -24,6 +25,7 @@ export default {
components: {
UserAvatarLink,
CommitComponent,
+ Icon,
ActionsComponent,
ExternalUrlComponent,
StopComponent,
@@ -448,6 +450,10 @@ export default {
this.canRetry
);
},
+
+ folderIconName() {
+ return this.model.isOpen ? 'chevron-down' : 'chevron-right';
+ },
},
methods: {
@@ -494,27 +500,15 @@ export default {
role="button"
@click="onClickFolder">
- <span class="folder-icon">
- <i
- v-show="model.isOpen"
- class="fa fa-caret-down"
- aria-hidden="true"
- >
- </i>
- <i
- v-show="!model.isOpen"
- class="fa fa-caret-right"
- aria-hidden="true"
- >
- </i>
- </span>
+ <icon
+ :name="folderIconName"
+ class="folder-icon"
+ />
- <span class="folder-icon">
- <i
- class="fa fa-folder"
- aria-hidden="true">
- </i>
- </span>
+ <icon
+ name="folder"
+ class="folder-icon"
+ />
<span>
{{ model.folderName }}
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue b/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
index a6a265eb3fd..14c223c61a4 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
@@ -1,10 +1,14 @@
<script>
import _ from 'underscore';
import { mapActions } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
import frequentItemsMixin from './frequent_items_mixin';
export default {
+ components: {
+ Icon,
+ },
mixins: [frequentItemsMixin],
data() {
return {
@@ -45,11 +49,10 @@ export default {
type="search"
class="form-control"
/>
- <i
+ <icon
v-if="!searchQuery"
- class="search-icon fa fa-fw fa-search"
- aria-hidden="true"
- >
- </i>
+ name="search"
+ class="search-icon"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
index ee8eb206980..802827fce76 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
@@ -117,7 +117,7 @@ export default {
<button
:disabled="!hasChanges"
type="button"
- class="btn btn-primary btn-sm btn-block"
+ class="btn btn-primary btn-sm btn-block qa-begin-commit-button"
@click="toggleIsSmall"
>
{{ __('Commit…') }}
@@ -147,7 +147,7 @@ export default {
<loading-button
:loading="submitCommitLoading"
:label="commitButtonText"
- container-class="btn btn-success btn-sm float-left"
+ container-class="btn btn-success btn-sm float-left qa-commit-button"
@click="commitChanges"
/>
<button
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
index d376a004e84..699fa7dc937 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
@@ -38,14 +38,18 @@ export default {
return this.modifiedFilesLength ? 'multi-file-modified' : '';
},
additionsTooltip() {
- return sprintf(n__('1 %{type} addition', '%{count} %{type} additions', this.addedFilesLength), {
- type: this.title.toLowerCase(),
- count: this.addedFilesLength,
- });
+ return sprintf(
+ n__('1 %{type} addition', '%{count} %{type} additions', this.addedFilesLength),
+ {
+ type: this.title.toLowerCase(),
+ count: this.addedFilesLength,
+ },
+ );
},
modifiedTooltip() {
return sprintf(
- n__('1 %{type} modification', '%{count} %{type} modifications', this.modifiedFilesLength), {
+ n__('1 %{type} modification', '%{count} %{type} modifications', this.modifiedFilesLength),
+ {
type: this.title.toLowerCase(),
count: this.modifiedFilesLength,
},
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
index 8a1836a5c92..adf4b479c97 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
@@ -25,10 +25,7 @@ export default {
return `discard-file-${this.path}`;
},
modalTitle() {
- return sprintf(
- __('Discard changes to %{path}?'),
- { path: this.path },
- );
+ return sprintf(__('Discard changes to %{path}?'), { path: this.path });
},
},
methods: {
diff --git a/app/assets/javascripts/ide/components/file_templates/bar.vue b/app/assets/javascripts/ide/components/file_templates/bar.vue
index 23be5f45f16..3587626c580 100644
--- a/app/assets/javascripts/ide/components/file_templates/bar.vue
+++ b/app/assets/javascripts/ide/components/file_templates/bar.vue
@@ -47,7 +47,7 @@ export default {
</script>
<template>
- <div class="d-flex align-items-center ide-file-templates">
+ <div class="d-flex align-items-center ide-file-templates qa-file-templates-bar">
<strong class="append-right-default">
{{ __('File templates') }}
</strong>
@@ -63,7 +63,7 @@ export default {
:is-async-data="true"
:searchable="true"
:title="__('File templates')"
- class="mr-2"
+ class="mr-2 qa-file-template-dropdown"
@click="selectTemplate"
/>
<transition name="fade">
diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
index ef1f6de3a86..94222c08e91 100644
--- a/app/assets/javascripts/ide/components/file_templates/dropdown.vue
+++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
@@ -92,7 +92,7 @@ export default {
v-model="search"
:placeholder="__('Filter...')"
type="search"
- class="dropdown-input-field"
+ class="dropdown-input-field qa-dropdown-filter-input"
/>
<i
aria-hidden="true"
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
index f99ff6d6da8..dc84ee12f1e 100644
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -24,13 +24,7 @@ export default {
IdeProjectHeader,
},
computed: {
- ...mapState([
- 'loading',
- 'currentActivityView',
- 'changedFiles',
- 'stagedFiles',
- 'lastCommitMsg',
- ]),
+ ...mapState(['loading', 'currentActivityView', 'changedFiles', 'stagedFiles', 'lastCommitMsg']),
...mapGetters(['currentProject', 'someUncommitedChanges']),
showSuccessMessage() {
return (
diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue
index 39d46a91731..9f9e638f1aa 100644
--- a/app/assets/javascripts/ide/components/ide_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_tree.vue
@@ -45,7 +45,7 @@ export default {
<new-entry-button
:label="__('New file')"
:show-label="false"
- class="d-flex border-0 p-0 mr-3"
+ class="d-flex border-0 p-0 mr-3 qa-new-file"
icon="doc-new"
@click="openNewEntryModal({ type: 'blob' })"
/>
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index cfe25084b42..e88f01fb4f4 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -43,7 +43,7 @@ export default {
<template>
<div
- class="ide-file-list"
+ class="ide-file-list qa-file-list"
>
<template v-if="showLoading">
<div
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index c8343e77860..f5e42e87f1b 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -37,14 +37,10 @@ export default {
return this.hasSearchFocus && !this.search && !this.currentSearchType;
},
type() {
- return this.currentSearchType
- ? this.currentSearchType.type
- : '';
+ return this.currentSearchType ? this.currentSearchType.type : '';
},
searchTokens() {
- return this.currentSearchType
- ? [this.currentSearchType]
- : [];
+ return this.currentSearchType ? [this.currentSearchType] : [];
},
},
watch: {
diff --git a/app/assets/javascripts/ide/components/nav_dropdown_button.vue b/app/assets/javascripts/ide/components/nav_dropdown_button.vue
index 7f98769d484..6cee4e9a8f0 100644
--- a/app/assets/javascripts/ide/components/nav_dropdown_button.vue
+++ b/app/assets/javascripts/ide/components/nav_dropdown_button.vue
@@ -13,9 +13,7 @@ export default {
computed: {
...mapState(['currentBranchId', 'currentMergeRequestId']),
mergeRequestLabel() {
- return this.currentMergeRequestId
- ? `!${this.currentMergeRequestId}`
- : EMPTY_LABEL;
+ return this.currentMergeRequestId ? `!${this.currentMergeRequestId}` : EMPTY_LABEL;
},
branchLabel() {
return this.currentBranchId || EMPTY_LABEL;
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index bcd53ac1ba2..f0a04011a3e 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -110,12 +110,12 @@ export default {
ref="fieldName"
v-model="entryName"
type="text"
- class="form-control"
+ class="form-control qa-full-file-path"
placeholder="/dir/file_name"
/>
<ul
v-if="isCreatingNew"
- class="prepend-top-default list-inline"
+ class="prepend-top-default list-inline qa-template-list"
>
<li
v-for="(template, index) in templateTypes"
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index bd07f372177..10aa96dffaf 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -43,34 +43,25 @@ export default {
{
show: this.currentMergeRequestId,
title: __('Merge Request'),
- views: [
- rightSidebarViews.mergeRequestInfo,
- ],
+ views: [rightSidebarViews.mergeRequestInfo],
icon: 'text-description',
},
{
show: true,
title: __('Pipelines'),
- views: [
- rightSidebarViews.pipelines,
- rightSidebarViews.jobsDetail,
- ],
+ views: [rightSidebarViews.pipelines, rightSidebarViews.jobsDetail],
icon: 'rocket',
},
{
show: this.showLivePreview,
title: __('Live preview'),
- views: [
- rightSidebarViews.clientSidePreview,
- ],
+ views: [rightSidebarViews.clientSidePreview],
icon: 'live-preview',
},
];
},
tabs() {
- return this.defaultTabs
- .concat(this.extensionTabs)
- .filter(tab => tab.show);
+ return this.defaultTabs.concat(this.extensionTabs).filter(tab => tab.show);
},
tabViews() {
return _.flatten(this.tabs.map(tab => tab.views));
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index b2599128213..7b0f717962e 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -25,12 +25,7 @@ export default {
...mapState('rightPane', {
rightPaneIsOpen: 'isOpen',
}),
- ...mapState([
- 'rightPanelCollapsed',
- 'viewer',
- 'panelResizing',
- 'currentActivityView',
- ]),
+ ...mapState(['rightPanelCollapsed', 'viewer', 'panelResizing', 'currentActivityView']),
...mapGetters([
'currentMergeRequest',
'getStagedFile',
diff --git a/app/assets/javascripts/ide/components/shared/tokened_input.vue b/app/assets/javascripts/ide/components/shared/tokened_input.vue
index a7a12f6785d..30010957a16 100644
--- a/app/assets/javascripts/ide/components/shared/tokened_input.vue
+++ b/app/assets/javascripts/ide/components/shared/tokened_input.vue
@@ -30,9 +30,7 @@ export default {
},
computed: {
placeholderText() {
- return this.tokens.length
- ? ''
- : this.placeholder;
+ return this.tokens.length ? '' : this.placeholder;
},
},
watch: {
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index c0550116633..7a5a227db30 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -21,10 +21,7 @@ Vue.use(Translate);
export function initIde(el, options = {}) {
if (!el) return null;
- const {
- extraInitialData = () => ({}),
- rootComponent = ide,
- } = options;
+ const { extraInitialData = () => ({}), rootComponent = ide } = options;
return new Vue({
el,
diff --git a/app/assets/javascripts/ide/lib/diff/diff.js b/app/assets/javascripts/ide/lib/diff/diff.js
index 0e37f5c4704..9b7ed68b893 100644
--- a/app/assets/javascripts/ide/lib/diff/diff.js
+++ b/app/assets/javascripts/ide/lib/diff/diff.js
@@ -11,14 +11,16 @@ export const computeDiff = (originalContent, newContent) => {
if (findOnLine) {
Object.assign(findOnLine, change, {
modified: true,
- endLineNumber: (lineNumber + change.count) - 1,
+ endLineNumber: lineNumber + change.count - 1,
});
} else if ('added' in change || 'removed' in change) {
- acc.push(Object.assign({}, change, {
- lineNumber,
- modified: undefined,
- endLineNumber: (lineNumber + change.count) - 1,
- }));
+ acc.push(
+ Object.assign({}, change, {
+ lineNumber,
+ modified: undefined,
+ endLineNumber: lineNumber + change.count - 1,
+ }),
+ );
}
if (!change.removed) {
diff --git a/app/assets/javascripts/ide/lib/diff/diff_worker.js b/app/assets/javascripts/ide/lib/diff/diff_worker.js
index 78b2eab6399..77416a8de9d 100644
--- a/app/assets/javascripts/ide/lib/diff/diff_worker.js
+++ b/app/assets/javascripts/ide/lib/diff/diff_worker.js
@@ -1,7 +1,7 @@
import { computeDiff } from './diff';
// eslint-disable-next-line no-restricted-globals
-self.addEventListener('message', (e) => {
+self.addEventListener('message', e => {
const { data } = e;
// eslint-disable-next-line no-restricted-globals
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
index 187f8c75d07..3ac2f8b3698 100644
--- a/app/assets/javascripts/ide/stores/actions/merge_request.js
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -116,57 +116,57 @@ export const openMergeRequest = (
targetProjectId,
mergeRequestId,
})
- .then(mr => {
- dispatch('setCurrentBranchId', mr.source_branch);
+ .then(mr => {
+ dispatch('setCurrentBranchId', mr.source_branch);
- dispatch('getBranchData', {
- projectId,
- branchId: mr.source_branch,
- });
+ dispatch('getBranchData', {
+ projectId,
+ branchId: mr.source_branch,
+ });
- return dispatch('getFiles', {
- projectId,
- branchId: mr.source_branch,
- });
- })
- .then(() =>
- dispatch('getMergeRequestVersions', {
- projectId,
- targetProjectId,
- mergeRequestId,
- }),
- )
- .then(() =>
- dispatch('getMergeRequestChanges', {
- projectId,
- targetProjectId,
- mergeRequestId,
- }),
- )
- .then(mrChanges => {
- if (mrChanges.changes.length) {
- dispatch('updateActivityBarView', activityBarViews.review);
- }
+ return dispatch('getFiles', {
+ projectId,
+ branchId: mr.source_branch,
+ });
+ })
+ .then(() =>
+ dispatch('getMergeRequestVersions', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(() =>
+ dispatch('getMergeRequestChanges', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(mrChanges => {
+ if (mrChanges.changes.length) {
+ dispatch('updateActivityBarView', activityBarViews.review);
+ }
- mrChanges.changes.forEach((change, ind) => {
- const changeTreeEntry = state.entries[change.new_path];
+ mrChanges.changes.forEach((change, ind) => {
+ const changeTreeEntry = state.entries[change.new_path];
- if (changeTreeEntry) {
- dispatch('setFileMrChange', {
- file: changeTreeEntry,
- mrChange: change,
- });
-
- if (ind < 10) {
- dispatch('getFileData', {
- path: change.new_path,
- makeFileActive: ind === 0,
+ if (changeTreeEntry) {
+ dispatch('setFileMrChange', {
+ file: changeTreeEntry,
+ mrChange: change,
});
+
+ if (ind < 10) {
+ dispatch('getFileData', {
+ path: change.new_path,
+ makeFileActive: ind === 0,
+ });
+ }
}
- }
+ });
+ })
+ .catch(e => {
+ flash(__('Error while loading the merge request. Please try again.'));
+ throw e;
});
- })
- .catch(e => {
- flash(__('Error while loading the merge request. Please try again.'));
- throw e;
- });
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index 543dc6c0461..2cb08ab2945 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -125,10 +125,7 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
});
};
-export const openBranch = (
- { dispatch, state },
- { projectId, branchId, basePath },
-) => {
+export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath }) => {
dispatch('setCurrentBranchId', branchId);
dispatch('getBranchData', {
@@ -136,23 +133,20 @@ export const openBranch = (
branchId,
});
- return (
- dispatch('getFiles', {
- projectId,
- branchId,
- })
- .then(() => {
- if (basePath) {
- const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
- const treeEntryKey = Object.keys(state.entries).find(
- key => key === path && !state.entries[key].pending,
- );
- const treeEntry = state.entries[treeEntryKey];
+ return dispatch('getFiles', {
+ projectId,
+ branchId,
+ }).then(() => {
+ if (basePath) {
+ const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
+ const treeEntryKey = Object.keys(state.entries).find(
+ key => key === path && !state.entries[key].pending,
+ );
+ const treeEntry = state.entries[treeEntryKey];
- if (treeEntry) {
- dispatch('handleTreeEntryAction', treeEntry);
- }
+ if (treeEntry) {
+ dispatch('handleTreeEntryAction', treeEntry);
}
- })
- );
+ }
+ });
};
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
index d519c033769..25a65b047f1 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
@@ -13,6 +13,7 @@ export default {
},
[types.SET_SELECTED_TEMPLATE_TYPE](state, type) {
state.selectedTemplateType = type;
+ state.templates = [];
},
[types.SET_UPDATE_SUCCESS](state, success) {
state.updateSuccess = success;
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
index baa2497ec5b..4565c11a83f 100644
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
@@ -3,8 +3,7 @@ import Api from '../../../../api';
import { scopes } from './constants';
import * as types from './mutation_types';
-export const requestMergeRequests = ({ commit }) =>
- commit(types.REQUEST_MERGE_REQUESTS);
+export const requestMergeRequests = ({ commit }) => commit(types.REQUEST_MERGE_REQUESTS);
export const receiveMergeRequestsError = ({ commit, dispatch }, { type, search }) => {
dispatch(
'setErrorMessage',
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index c6ad3aa3e0d..04c1cf021d9 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -1,281 +1,279 @@
<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 {
- components: {
- descriptionComponent,
- titleComponent,
- editedComponent,
- formComponent,
- },
- 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,
- },
- markdownVersion: {
- type: Number,
- required: false,
- default: 0,
- },
- projectPath: {
- type: String,
- required: true,
- },
- projectNamespace: {
- type: String,
- required: true,
- },
- issuableType: {
- type: String,
- required: false,
- default: 'issue',
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
+export default {
+ components: {
+ descriptionComponent,
+ titleComponent,
+ editedComponent,
+ formComponent,
+ },
+ mixins: [recaptchaModalImplementor],
+ props: {
+ endpoint: {
+ required: true,
+ type: String,
},
- 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,
- });
+ 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,
+ },
+ markdownVersion: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ 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,
+ });
- return {
- store,
- state: store.state,
- showForm: false,
- };
- },
- computed: {
- formState() {
- return this.store.formState;
- },
- hasUpdated() {
- return !!this.state.updatedAt;
- },
- issueChanged() {
- const descriptionChanged =
- this.initialDescriptionText !== this.store.formState.description;
- const titleChanged =
- this.initialTitleText !== this.store.formState.title;
- return descriptionChanged || titleChanged;
- },
+ return {
+ store,
+ state: store.state,
+ showForm: false,
+ };
+ },
+ computed: {
+ formState() {
+ return this.store.formState;
},
- 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);
- },
- });
+ hasUpdated() {
+ return !!this.state.updatedAt;
+ },
+ issueChanged() {
+ const descriptionChanged = this.initialDescriptionText !== this.store.formState.description;
+ const titleChanged = this.initialTitleText !== this.store.formState.title;
+ return descriptionChanged || titleChanged;
+ },
+ },
+ 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);
+ },
+ });
+
+ if (!Visibility.hidden()) {
+ this.poll.makeRequest();
+ }
+ Visibility.change(() => {
if (!Visibility.hidden()) {
- this.poll.makeRequest();
+ this.poll.restart();
+ } else {
+ this.poll.stop();
}
+ });
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- this.poll.restart();
- } else {
- this.poll.stop();
- }
- });
-
- window.addEventListener('beforeunload', this.handleBeforeUnloadEvent);
+ window.addEventListener('beforeunload', this.handleBeforeUnloadEvent);
- 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);
- window.removeEventListener('beforeunload', this.handleBeforeUnloadEvent);
- },
- methods: {
- handleBeforeUnloadEvent(e) {
- const event = e;
- if (this.showForm && this.issueChanged) {
- event.returnValue = 'Are you sure you want to lose your issue information?';
- }
- return undefined;
- },
+ 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);
+ window.removeEventListener('beforeunload', this.handleBeforeUnloadEvent);
+ },
+ methods: {
+ handleBeforeUnloadEvent(e) {
+ const event = e;
+ if (this.showForm && this.issueChanged) {
+ event.returnValue = 'Are you sure you want to lose your issue information?';
+ }
+ return undefined;
+ },
- 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;
- },
+ 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;
+ },
- updateIssuable() {
- return this.service.updateIssuable(this.store.formState)
- .then(res => res.data)
- .then(data => this.checkForSpam(data))
- .then((data) => {
- if (window.location.pathname !== data.web_url) {
- visitUrl(data.web_url);
- }
+ updateIssuable() {
+ return this.service
+ .updateIssuable(this.store.formState)
+ .then(res => res.data)
+ .then(data => this.checkForSpam(data))
+ .then(data => {
+ if (window.location.pathname !== data.web_url) {
+ visitUrl(data.web_url);
+ }
- return this.service.getData();
- })
- .then(res => res.data)
- .then((data) => {
- this.store.updateState(data);
+ 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');
- })
- .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,
+ window.Flash(`Error updating ${this.issuableType}`);
+ }
});
+ },
- this.closeRecaptcha();
- },
+ closeRecaptchaModal() {
+ this.store.setFormState({
+ updateLoading: 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();
+ this.closeRecaptcha();
+ },
- visitUrl(data.web_url);
- })
- .catch(() => {
- eventHub.$emit('close.form');
- window.Flash(`Error deleting ${this.issuableType}`);
- });
- },
+ 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();
+
+ visitUrl(data.web_url);
+ })
+ .catch(() => {
+ eventHub.$emit('close.form');
+ window.Flash(`Error deleting ${this.issuableType}`);
+ });
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 1174177f561..461cb3271b7 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,110 +1,105 @@
<script>
- import $ from 'jquery';
- import animateMixin from '../mixins/animate';
- import TaskList from '../../task_list';
- import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
+import $ from 'jquery';
+import animateMixin from '../mixins/animate';
+import TaskList from '../../task_list';
+import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
- export default {
- mixins: [
- animateMixin,
- recaptchaModalImplementor,
- ],
+export default {
+ mixins: [animateMixin, recaptchaModalImplementor],
- props: {
- canUpdate: {
- type: Boolean,
- required: true,
- },
- descriptionHtml: {
- type: String,
- required: true,
- },
- descriptionText: {
- type: String,
- required: true,
- },
- taskStatus: {
- type: String,
- required: false,
- default: '',
- },
- issuableType: {
- type: String,
- required: false,
- default: 'issue',
- },
- updateUrl: {
- type: String,
- required: false,
- default: null,
- },
+ props: {
+ canUpdate: {
+ type: Boolean,
+ required: true,
},
- data() {
- return {
- preAnimation: false,
- pulseAnimation: false,
- };
+ descriptionHtml: {
+ type: String,
+ required: true,
},
- watch: {
- descriptionHtml() {
- this.animateChange();
+ descriptionText: {
+ type: String,
+ required: true,
+ },
+ taskStatus: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ issuableType: {
+ type: String,
+ required: false,
+ default: 'issue',
+ },
+ updateUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ preAnimation: false,
+ pulseAnimation: false,
+ };
+ },
+ watch: {
+ descriptionHtml() {
+ this.animateChange();
- this.$nextTick(() => {
- this.renderGFM();
- });
- },
- taskStatus() {
- this.updateTaskStatusText();
- },
+ this.$nextTick(() => {
+ this.renderGFM();
+ });
},
- mounted() {
- this.renderGFM();
+ taskStatus() {
this.updateTaskStatusText();
},
- methods: {
- renderGFM() {
- $(this.$refs['gfm-content']).renderGFM();
+ },
+ mounted() {
+ this.renderGFM();
+ this.updateTaskStatusText();
+ },
+ methods: {
+ renderGFM() {
+ $(this.$refs['gfm-content']).renderGFM();
- if (this.canUpdate) {
- // eslint-disable-next-line no-new
- new TaskList({
- dataType: this.issuableType,
- fieldName: 'description',
- selector: '.detail-page-description',
- onSuccess: this.taskListUpdateSuccess.bind(this),
- });
- }
- },
+ if (this.canUpdate) {
+ // eslint-disable-next-line no-new
+ new TaskList({
+ dataType: this.issuableType,
+ fieldName: 'description',
+ selector: '.detail-page-description',
+ onSuccess: this.taskListUpdateSuccess.bind(this),
+ });
+ }
+ },
- taskListUpdateSuccess(data) {
- try {
- this.checkForSpam(data);
- this.closeRecaptcha();
- } catch (error) {
- if (error && error.name === 'SpamError') this.openRecaptcha();
- }
- },
+ taskListUpdateSuccess(data) {
+ try {
+ this.checkForSpam(data);
+ this.closeRecaptcha();
+ } catch (error) {
+ if (error && error.name === 'SpamError') this.openRecaptcha();
+ }
+ },
- updateTaskStatusText() {
- const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
- const $issuableHeader = $('.issuable-meta');
- const $tasks = $('#task_status', $issuableHeader);
- const $tasksShort = $('#task_status_short', $issuableHeader);
+ updateTaskStatusText() {
+ const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
+ const $issuableHeader = $('.issuable-meta');
+ const $tasks = $('#task_status', $issuableHeader);
+ const $tasksShort = $('#task_status_short', $issuableHeader);
- if (taskRegexMatches) {
- $tasks.text(this.taskStatus);
- $tasksShort.text(
- `${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ?
- 's' :
- ''}`,
- );
- } else {
- $tasks.text('');
- $tasksShort.text('');
- }
- },
+ if (taskRegexMatches) {
+ $tasks.text(this.taskStatus);
+ $tasksShort.text(
+ `${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`,
+ );
+ } else {
+ $tasks.text('');
+ $tasksShort.text('');
+ }
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue
index c3d39082714..5dda35d64bb 100644
--- a/app/assets/javascripts/issue_show/components/edit_actions.vue
+++ b/app/assets/javascripts/issue_show/components/edit_actions.vue
@@ -1,64 +1,64 @@
<script>
- import { __, sprintf } from '~/locale';
- import updateMixin from '../mixins/update';
- import eventHub from '../event_hub';
+import { __, sprintf } from '~/locale';
+import updateMixin from '../mixins/update';
+import eventHub from '../event_hub';
- const issuableTypes = {
- issue: __('Issue'),
- epic: __('Epic'),
- };
+const issuableTypes = {
+ issue: __('Issue'),
+ epic: __('Epic'),
+};
- export default {
- mixins: [updateMixin],
- props: {
- canDestroy: {
- type: Boolean,
- required: true,
- },
- formState: {
- type: Object,
- required: true,
- },
- showDeleteButton: {
- type: Boolean,
- required: false,
- default: true,
- },
- issuableType: {
- type: String,
- required: true,
- },
+export default {
+ mixins: [updateMixin],
+ props: {
+ canDestroy: {
+ type: Boolean,
+ required: true,
},
- data() {
- return {
- deleteLoading: false,
- };
+ formState: {
+ type: Object,
+ required: true,
},
- computed: {
- isSubmitEnabled() {
- return this.formState.title.trim() !== '';
- },
- shouldShowDeleteButton() {
- return this.canDestroy && this.showDeleteButton;
- },
+ showDeleteButton: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- methods: {
- closeForm() {
- eventHub.$emit('close.form');
- },
- deleteIssuable() {
- const confirmMessage = sprintf(__('%{issuableType} will be removed! Are you sure?'), {
- issuableType: issuableTypes[this.issuableType],
- });
- // eslint-disable-next-line no-alert
- if (window.confirm(confirmMessage)) {
- this.deleteLoading = true;
+ issuableType: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ deleteLoading: false,
+ };
+ },
+ computed: {
+ isSubmitEnabled() {
+ return this.formState.title.trim() !== '';
+ },
+ shouldShowDeleteButton() {
+ return this.canDestroy && this.showDeleteButton;
+ },
+ },
+ methods: {
+ closeForm() {
+ eventHub.$emit('close.form');
+ },
+ deleteIssuable() {
+ const confirmMessage = sprintf(__('%{issuableType} will be removed! Are you sure?'), {
+ issuableType: issuableTypes[this.issuableType],
+ });
+ // eslint-disable-next-line no-alert
+ if (window.confirm(confirmMessage)) {
+ this.deleteLoading = true;
- eventHub.$emit('delete.issuable');
- }
- },
+ eventHub.$emit('delete.issuable');
+ }
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/edited.vue b/app/assets/javascripts/issue_show/components/edited.vue
index 05cd976f196..73ecb26c28d 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 {
- components: {
- timeAgoTooltip,
+export default {
+ components: {
+ timeAgoTooltip,
+ },
+ props: {
+ updatedAt: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- updatedAt: {
- type: String,
- required: false,
- default: '',
- },
- updatedByName: {
- type: String,
- required: false,
- default: '',
- },
- updatedByPath: {
- type: String,
- required: false,
- default: '',
- },
+ updatedByName: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- hasUpdatedBy() {
- return this.updatedByName && this.updatedByPath;
- },
+ updatedByPath: {
+ type: String,
+ required: false,
+ default: '',
},
- };
+ },
+ computed: {
+ hasUpdatedBy() {
+ return this.updatedByName && this.updatedByPath;
+ },
+ },
+};
</script>
<template>
@@ -53,4 +53,3 @@
</span>
</small>
</template>
-
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
index 1a78c59d715..e9e96a985a7 100644
--- a/app/assets/javascripts/issue_show/components/fields/description.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -1,45 +1,45 @@
<script>
- import updateMixin from '../../mixins/update';
- import markdownField from '../../../vue_shared/components/markdown/field.vue';
+import updateMixin from '../../mixins/update';
+import markdownField from '../../../vue_shared/components/markdown/field.vue';
- export default {
- components: {
- markdownField,
+export default {
+ components: {
+ markdownField,
+ },
+ mixins: [updateMixin],
+ props: {
+ formState: {
+ type: Object,
+ required: true,
},
- mixins: [updateMixin],
- props: {
- formState: {
- type: Object,
- required: true,
- },
- markdownPreviewPath: {
- type: String,
- required: true,
- },
- markdownDocsPath: {
- type: String,
- required: true,
- },
- markdownVersion: {
- type: Number,
- required: false,
- default: 0,
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
- enableAutocomplete: {
- type: Boolean,
- required: false,
- default: true,
- },
+ markdownPreviewPath: {
+ type: String,
+ required: true,
},
- mounted() {
- this.$refs.textarea.focus();
+ markdownDocsPath: {
+ type: String,
+ required: true,
},
- };
+ markdownVersion: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ enableAutocomplete: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ mounted() {
+ this.$refs.textarea.focus();
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/fields/description_template.vue b/app/assets/javascripts/issue_show/components/fields/description_template.vue
index e90d9fad94e..e433bf66cfc 100644
--- a/app/assets/javascripts/issue_show/components/fields/description_template.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description_template.vue
@@ -1,46 +1,46 @@
<script>
- import $ from 'jquery';
- import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
+import $ from 'jquery';
+import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
- export default {
- props: {
- formState: {
- type: Object,
- required: true,
- },
- issuableTemplates: {
- type: Array,
- required: false,
- default: () => [],
- },
- projectPath: {
- type: String,
- required: true,
- },
- projectNamespace: {
- type: String,
- required: true,
- },
+export default {
+ props: {
+ formState: {
+ type: Object,
+ required: true,
},
- computed: {
- issuableTemplatesJson() {
- return JSON.stringify(this.issuableTemplates);
- },
+ issuableTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
},
- mounted() {
- // Create the editor for the template
- const editor = document.querySelector('.detail-page-description .note-textarea') || {};
- editor.setValue = (val) => {
- this.formState.description = val;
- };
- editor.getValue = () => this.formState.description;
-
- this.issuableTemplate = new IssuableTemplateSelectors({
- $dropdowns: $(this.$refs.toggle),
- editor,
- });
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
},
- };
+ },
+ computed: {
+ issuableTemplatesJson() {
+ return JSON.stringify(this.issuableTemplates);
+ },
+ },
+ mounted() {
+ // Create the editor for the template
+ const editor = document.querySelector('.detail-page-description .note-textarea') || {};
+ editor.setValue = val => {
+ this.formState.description = val;
+ };
+ editor.getValue = () => this.formState.description;
+
+ this.issuableTemplate = new IssuableTemplateSelectors({
+ $dropdowns: $(this.$refs.toggle),
+ editor,
+ });
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/fields/title.vue b/app/assets/javascripts/issue_show/components/fields/title.vue
index b7f2b1a6050..11f4153b8d5 100644
--- a/app/assets/javascripts/issue_show/components/fields/title.vue
+++ b/app/assets/javascripts/issue_show/components/fields/title.vue
@@ -1,15 +1,15 @@
<script>
- import updateMixin from '../../mixins/update';
+import updateMixin from '../../mixins/update';
- export default {
- mixins: [updateMixin],
- props: {
- formState: {
- type: Object,
- required: true,
- },
+export default {
+ mixins: [updateMixin],
+ props: {
+ formState: {
+ type: Object,
+ required: true,
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
index 03d8d0ec67c..3b430d92912 100644
--- a/app/assets/javascripts/issue_show/components/form.vue
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -1,79 +1,79 @@
<script>
- import lockedWarning from './locked_warning.vue';
- import titleField from './fields/title.vue';
- import descriptionField from './fields/description.vue';
- import editActions from './edit_actions.vue';
- import descriptionTemplate from './fields/description_template.vue';
+import lockedWarning from './locked_warning.vue';
+import titleField from './fields/title.vue';
+import descriptionField from './fields/description.vue';
+import editActions from './edit_actions.vue';
+import descriptionTemplate from './fields/description_template.vue';
- export default {
- components: {
- lockedWarning,
- titleField,
- descriptionField,
- descriptionTemplate,
- editActions,
+export default {
+ components: {
+ lockedWarning,
+ titleField,
+ descriptionField,
+ descriptionTemplate,
+ editActions,
+ },
+ props: {
+ canDestroy: {
+ type: Boolean,
+ required: true,
},
- props: {
- canDestroy: {
- type: Boolean,
- required: true,
- },
- formState: {
- type: Object,
- required: true,
- },
- issuableTemplates: {
- type: Array,
- required: false,
- default: () => [],
- },
- issuableType: {
- type: String,
- required: true,
- },
- markdownPreviewPath: {
- type: String,
- required: true,
- },
- markdownDocsPath: {
- type: String,
- required: true,
- },
- markdownVersion: {
- type: Number,
- required: false,
- default: 0,
- },
- projectPath: {
- type: String,
- required: true,
- },
- projectNamespace: {
- type: String,
- required: true,
- },
- showDeleteButton: {
- type: Boolean,
- required: false,
- default: true,
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
- enableAutocomplete: {
- type: Boolean,
- required: false,
- default: true,
- },
+ formState: {
+ type: Object,
+ required: true,
},
- computed: {
- hasIssuableTemplates() {
- return this.issuableTemplates.length;
- },
+ issuableTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
},
- };
+ issuableType: {
+ type: String,
+ required: true,
+ },
+ markdownPreviewPath: {
+ type: String,
+ required: true,
+ },
+ markdownDocsPath: {
+ type: String,
+ required: true,
+ },
+ markdownVersion: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
+ },
+ showDeleteButton: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ enableAutocomplete: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ computed: {
+ hasIssuableTemplates() {
+ return this.issuableTemplates.length;
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/locked_warning.vue b/app/assets/javascripts/issue_show/components/locked_warning.vue
index ad0d40faf32..0682c6f2a35 100644
--- a/app/assets/javascripts/issue_show/components/locked_warning.vue
+++ b/app/assets/javascripts/issue_show/components/locked_warning.vue
@@ -1,11 +1,11 @@
<script>
- export default {
- computed: {
- currentPath() {
- return window.location.pathname;
- },
+export default {
+ computed: {
+ currentPath() {
+ return window.location.pathname;
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index af8b0414266..32044d6da25 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -25,8 +25,10 @@ export default class Store {
}
stateShouldUpdate(data) {
- return this.state.titleText !== data.title_text ||
- this.state.descriptionText !== data.description_text;
+ return (
+ this.state.titleText !== data.title_text ||
+ this.state.descriptionText !== data.description_text
+ );
}
setFormState(state) {
diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue
index d5866f9b9f1..17fd5321642 100644
--- a/app/assets/javascripts/jobs/components/artifacts_block.vue
+++ b/app/assets/javascripts/jobs/components/artifacts_block.vue
@@ -1,30 +1,28 @@
<script>
- import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
- import timeagoMixin from '~/vue_shared/mixins/timeago';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
- export default {
- components: {
- TimeagoTooltip,
+export default {
+ components: {
+ TimeagoTooltip,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ artifact: {
+ type: Object,
+ required: true,
},
- mixins: [
- timeagoMixin,
- ],
- props: {
- artifact: {
- type: Object,
- required: true,
- },
+ },
+ computed: {
+ isExpired() {
+ return this.artifact.expired;
},
- computed: {
- isExpired() {
- return this.artifact.expired;
- },
- // Only when the key is `false` we can render this block
- willExpire() {
- return this.artifact.expired === false;
- },
+ // Only when the key is `false` we can render this block
+ willExpire() {
+ return this.artifact.expired === false;
},
- };
+ },
+};
</script>
<template>
<div class="block">
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
index 4b1788a1c16..7d51f6afd10 100644
--- a/app/assets/javascripts/jobs/components/commit_block.vue
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -1,26 +1,26 @@
<script>
- import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
- export default {
- components: {
- ClipboardButton,
+export default {
+ components: {
+ ClipboardButton,
+ },
+ props: {
+ commit: {
+ type: Object,
+ required: true,
},
- props: {
- commit: {
- type: Object,
- required: true,
- },
- mergeRequest: {
- type: Object,
- required: false,
- default: null,
- },
- isLastBlock: {
- type: Boolean,
- required: true,
- },
+ mergeRequest: {
+ type: Object,
+ required: false,
+ default: null,
},
- };
+ isLastBlock: {
+ type: Boolean,
+ required: true,
+ },
+ },
+};
</script>
<template>
<div
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
index ff500eddddb..ee5ceb99b0a 100644
--- a/app/assets/javascripts/jobs/components/empty_state.vue
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -1,38 +1,38 @@
<script>
- export default {
- props: {
- illustrationPath: {
- type: String,
- required: true,
- },
- illustrationSizeClass: {
- type: String,
- required: true,
- },
- title: {
- type: String,
- required: true,
- },
- content: {
- type: String,
- required: false,
- default: null,
- },
- action: {
- type: Object,
- required: false,
- default: null,
- validator(value) {
- return (
- value === null ||
- (Object.prototype.hasOwnProperty.call(value, 'path') &&
- Object.prototype.hasOwnProperty.call(value, 'method') &&
- Object.prototype.hasOwnProperty.call(value, 'button_title'))
- );
- },
+export default {
+ props: {
+ illustrationPath: {
+ type: String,
+ required: true,
+ },
+ illustrationSizeClass: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ content: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ action: {
+ type: Object,
+ required: false,
+ default: null,
+ validator(value) {
+ return (
+ value === null ||
+ (Object.prototype.hasOwnProperty.call(value, 'path') &&
+ Object.prototype.hasOwnProperty.call(value, 'method') &&
+ Object.prototype.hasOwnProperty.call(value, 'button_title'))
+ );
},
},
- };
+ },
+};
</script>
<template>
<div class="row empty-state">
diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue
index e6e1d418194..6d1eb713886 100644
--- a/app/assets/javascripts/jobs/components/environments_block.vue
+++ b/app/assets/javascripts/jobs/components/environments_block.vue
@@ -1,129 +1,131 @@
<script>
- import _ from 'underscore';
- import CiIcon from '~/vue_shared/components/ci_icon.vue';
- import { sprintf, __ } from '../../locale';
+import _ from 'underscore';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import { sprintf, __ } from '../../locale';
- export default {
- components: {
- CiIcon,
+export default {
+ components: {
+ CiIcon,
+ },
+ props: {
+ deploymentStatus: {
+ type: Object,
+ required: true,
},
- props: {
- deploymentStatus: {
- type: Object,
- required: true,
- },
- iconStatus: {
- type: Object,
- required: true,
- },
+ iconStatus: {
+ type: Object,
+ required: true,
},
- computed: {
- environment() {
- let environmentText;
- switch (this.deploymentStatus.status) {
- case 'last':
+ },
+ computed: {
+ environment() {
+ let environmentText;
+ switch (this.deploymentStatus.status) {
+ case 'last':
+ environmentText = sprintf(
+ __('This job is the most recent deployment to %{link}.'),
+ { link: this.environmentLink },
+ false,
+ );
+ break;
+ case 'out_of_date':
+ if (this.hasLastDeployment) {
environmentText = sprintf(
- __('This job is the most recent deployment to %{link}.'),
- { link: this.environmentLink },
+ __(
+ 'This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.',
+ ),
+ {
+ environmentLink: this.environmentLink,
+ deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
+ },
false,
);
- break;
- case 'out_of_date':
- if (this.hasLastDeployment) {
- environmentText = sprintf(
- __(
- 'This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.',
- ),
- {
- environmentLink: this.environmentLink,
- deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
- },
- false,
- );
- } else {
- environmentText = sprintf(
- __('This job is an out-of-date deployment to %{environmentLink}.'),
- { environmentLink: this.environmentLink },
- false,
- );
- }
-
- break;
- case 'failed':
+ } else {
environmentText = sprintf(
- __('The deployment of this job to %{environmentLink} did not succeed.'),
+ __('This job is an out-of-date deployment to %{environmentLink}.'),
{ environmentLink: this.environmentLink },
false,
);
- break;
- case 'creating':
- if (this.hasLastDeployment) {
- environmentText = sprintf(
- __(
- 'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.',
- ),
- {
- environmentLink: this.environmentLink,
- deploymentLink: this.deploymentLink(__('latest deployment')),
- },
- false,
- );
- } else {
- environmentText = sprintf(
- __('This job is creating a deployment to %{environmentLink}.'),
- { environmentLink: this.environmentLink },
- false,
- );
- }
- break;
- default:
- break;
- }
- return environmentText;
- },
- environmentLink() {
- if (this.hasEnvironment) {
- return sprintf(
- '%{startLink}%{name}%{endLink}',
- {
- startLink: `<a href="${
- this.deploymentStatus.environment.environment_path
- }" class="js-environment-link">`,
- name: _.escape(this.deploymentStatus.environment.name),
- endLink: '</a>',
- },
+ }
+
+ break;
+ case 'failed':
+ environmentText = sprintf(
+ __('The deployment of this job to %{environmentLink} did not succeed.'),
+ { environmentLink: this.environmentLink },
false,
);
- }
- return '';
- },
- hasLastDeployment() {
- return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
- },
- lastDeployment() {
- return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
- },
- hasEnvironment() {
- return !_.isEmpty(this.deploymentStatus.environment);
- },
- lastDeploymentPath() {
- return !_.isEmpty(this.lastDeployment.deployable) ? this.lastDeployment.deployable.build_path : '';
- },
+ break;
+ case 'creating':
+ if (this.hasLastDeployment) {
+ environmentText = sprintf(
+ __(
+ 'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.',
+ ),
+ {
+ environmentLink: this.environmentLink,
+ deploymentLink: this.deploymentLink(__('latest deployment')),
+ },
+ false,
+ );
+ } else {
+ environmentText = sprintf(
+ __('This job is creating a deployment to %{environmentLink}.'),
+ { environmentLink: this.environmentLink },
+ false,
+ );
+ }
+ break;
+ default:
+ break;
+ }
+ return environmentText;
},
- methods: {
- deploymentLink(name) {
+ environmentLink() {
+ if (this.hasEnvironment) {
return sprintf(
'%{startLink}%{name}%{endLink}',
{
- startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
- name,
+ startLink: `<a href="${
+ this.deploymentStatus.environment.environment_path
+ }" class="js-environment-link">`,
+ name: _.escape(this.deploymentStatus.environment.name),
endLink: '</a>',
},
false,
);
- },
+ }
+ return '';
+ },
+ hasLastDeployment() {
+ return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
+ },
+ lastDeployment() {
+ return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
+ },
+ hasEnvironment() {
+ return !_.isEmpty(this.deploymentStatus.environment);
+ },
+ lastDeploymentPath() {
+ return !_.isEmpty(this.lastDeployment.deployable)
+ ? this.lastDeployment.deployable.build_path
+ : '';
+ },
+ },
+ methods: {
+ deploymentLink(name) {
+ return sprintf(
+ '%{startLink}%{name}%{endLink}',
+ {
+ startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
+ name,
+ endLink: '</a>',
+ },
+ false,
+ );
},
- };
+ },
+};
</script>
<template>
<div class="prepend-top-default js-environment-container">
diff --git a/app/assets/javascripts/jobs/components/erased_block.vue b/app/assets/javascripts/jobs/components/erased_block.vue
index 3d6d9ba4387..5ffbfb6e19a 100644
--- a/app/assets/javascripts/jobs/components/erased_block.vue
+++ b/app/assets/javascripts/jobs/components/erased_block.vue
@@ -1,28 +1,28 @@
<script>
- import _ from 'underscore';
- import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import _ from 'underscore';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
- export default {
- components: {
- TimeagoTooltip,
+export default {
+ components: {
+ TimeagoTooltip,
+ },
+ props: {
+ user: {
+ type: Object,
+ required: false,
+ default: () => ({}),
},
- props: {
- user: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- erasedAt: {
- type: String,
- required: true,
- },
+ erasedAt: {
+ type: String,
+ required: true,
},
- computed: {
- isErasedByUser() {
- return !_.isEmpty(this.user);
- },
+ },
+ computed: {
+ isErasedByUser() {
+ return !_.isEmpty(this.user);
},
- };
+ },
+};
</script>
<template>
<div class="prepend-top-default js-build-erased">
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 047e55866ce..4e8d3ad24cc 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -18,7 +18,7 @@
StuckBlock,
},
props: {
- runnerHelpUrl: {
+ runnerSettingsUrl: {
type: String,
required: false,
default: null,
@@ -30,7 +30,7 @@
'headerActions',
'headerTime',
'shouldRenderCalloutMessage',
- 'jobHasStarted',
+ 'shouldRenderTriggeredLabel',
'hasEnvironment',
'isJobStuck',
'hasTrace',
@@ -58,7 +58,7 @@
:user="job.user"
:actions="headerActions"
:has-sidebar-button="true"
- :should-render-triggered-label="jobHasStarted"
+ :should-render-triggered-label="shouldRenderTriggeredLabel"
:item-name="__('Job')"
/>
</div>
@@ -76,7 +76,7 @@
class="js-job-stuck"
:has-no-runners-for-project="job.runners.available"
:tags="job.tags"
- :runners-path="runnerHelpUrl"
+ :runners-path="runnerSettingsUrl"
/>
<environments-block
@@ -87,8 +87,8 @@
/>
<erased-block
- v-if="job.erased"
- class="js-job-erased"
+ v-if="job.erased_at"
+ class="js-job-erased-block"
:user="job.erased_by"
:erased-at="job.erased_at"
/>
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
index b12e963b60c..9d78d89239a 100644
--- a/app/assets/javascripts/jobs/components/job_log.vue
+++ b/app/assets/javascripts/jobs/components/job_log.vue
@@ -1,17 +1,17 @@
<script>
- export default {
- name: 'JobLog',
- props: {
- trace: {
- type: String,
- required: true,
- },
- isComplete: {
- type: Boolean,
- required: true,
- },
+export default {
+ name: 'JobLog',
+ props: {
+ trace: {
+ type: String,
+ required: true,
},
- };
+ isComplete: {
+ type: Boolean,
+ required: true,
+ },
+ },
+};
</script>
<template>
<pre class="build-trace">
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index 3e62ababea3..cc885ea8e1b 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -1,72 +1,71 @@
<script>
- import { polyfillSticky } from '~/lib/utils/sticky';
- import Icon from '~/vue_shared/components/icon.vue';
- import tooltip from '~/vue_shared/directives/tooltip';
- import { numberToHumanSize } from '~/lib/utils/number_utils';
- import { sprintf } from '~/locale';
+import { polyfillSticky } from '~/lib/utils/sticky';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { sprintf } from '~/locale';
- export default {
- components: {
- Icon,
+export default {
+ components: {
+ Icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ erasePath: {
+ type: String,
+ required: false,
+ default: null,
},
- directives: {
- tooltip,
+ size: {
+ type: Number,
+ required: true,
},
- props: {
- erasePath: {
- type: String,
- required: false,
- default: null,
- },
- size: {
- type: Number,
- required: true,
- },
- rawPath: {
- type: String,
- required: false,
- default: null,
- },
- isScrollTopDisabled: {
- type: Boolean,
- required: true,
- },
- isScrollBottomDisabled: {
- type: Boolean,
- required: true,
- },
- isScrollingDown: {
- type: Boolean,
- required: true,
- },
- isTraceSizeVisible: {
- type: Boolean,
- required: true,
- },
+ rawPath: {
+ type: String,
+ required: false,
+ default: null,
},
- computed: {
- jobLogSize() {
- return sprintf('Showing last %{size} of log -', {
- size: numberToHumanSize(this.size),
- });
- },
+ isScrollTopDisabled: {
+ type: Boolean,
+ required: true,
},
- mounted() {
- polyfillSticky(this.$el);
+ isScrollBottomDisabled: {
+ type: Boolean,
+ required: true,
},
- methods: {
- handleScrollToTop() {
- this.$emit('scrollJobLogTop');
- },
- handleScrollToBottom() {
- this.$emit('scrollJobLogBottom');
- },
+ isScrollingDown: {
+ type: Boolean,
+ required: true,
},
-
- };
+ isTraceSizeVisible: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ jobLogSize() {
+ return sprintf('Showing last %{size} of log -', {
+ size: numberToHumanSize(this.size),
+ });
+ },
+ },
+ mounted() {
+ polyfillSticky(this.$el);
+ },
+ methods: {
+ handleScrollToTop() {
+ this.$emit('scrollJobLogTop');
+ },
+ handleScrollToBottom() {
+ this.$emit('scrollJobLogBottom');
+ },
+ },
+};
</script>
<template>
- <div class="top-bar">
+ <div class="top-bar affix js-top-bar">
<!-- truncate information -->
<div class="js-truncated-info truncated-info d-none d-sm-block float-left">
<template v-if="isTraceSizeVisible">
diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue
index 271b7790d75..03f36ec5c8b 100644
--- a/app/assets/javascripts/jobs/components/jobs_container.vue
+++ b/app/assets/javascripts/jobs/components/jobs_container.vue
@@ -1,36 +1,36 @@
<script>
- import _ from 'underscore';
- import CiIcon from '~/vue_shared/components/ci_icon.vue';
- import Icon from '~/vue_shared/components/icon.vue';
- import tooltip from '~/vue_shared/directives/tooltip';
+import _ from 'underscore';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
- export default {
- components: {
- CiIcon,
- Icon,
+export default {
+ components: {
+ CiIcon,
+ Icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ jobs: {
+ type: Array,
+ required: true,
},
- directives: {
- tooltip,
+ jobId: {
+ type: Number,
+ required: true,
},
- props: {
- jobs: {
- type: Array,
- required: true,
- },
- jobId: {
- type: Number,
- required: true,
- },
+ },
+ methods: {
+ isJobActive(currentJobId) {
+ return this.jobId === currentJobId;
},
- methods: {
- isJobActive(currentJobId) {
- return this.jobId === currentJobId;
- },
- tooltipText(job) {
- return `${_.escape(job.name)} - ${job.status.tooltip}`;
- },
+ tooltipText(job) {
+ return `${_.escape(job.name)} - ${job.status.tooltip}`;
},
- };
+ },
+};
</script>
<template>
<div class="js-jobs-container builds-container">
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 22bcd402e72..8f3c6aced23 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -1,116 +1,116 @@
<script>
- import _ from 'underscore';
- import { mapActions, mapState } from 'vuex';
- import timeagoMixin from '~/vue_shared/mixins/timeago';
- import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
- import Icon from '~/vue_shared/components/icon.vue';
- import DetailRow from './sidebar_detail_row.vue';
- import ArtifactsBlock from './artifacts_block.vue';
- import TriggerBlock from './trigger_block.vue';
- import CommitBlock from './commit_block.vue';
- import StagesDropdown from './stages_dropdown.vue';
- import JobsContainer from './jobs_container.vue';
+import _ from 'underscore';
+import { mapActions, mapState } from 'vuex';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
+import Icon from '~/vue_shared/components/icon.vue';
+import DetailRow from './sidebar_detail_row.vue';
+import ArtifactsBlock from './artifacts_block.vue';
+import TriggerBlock from './trigger_block.vue';
+import CommitBlock from './commit_block.vue';
+import StagesDropdown from './stages_dropdown.vue';
+import JobsContainer from './jobs_container.vue';
- export default {
- name: 'JobSidebar',
- components: {
- ArtifactsBlock,
- CommitBlock,
- DetailRow,
- Icon,
- TriggerBlock,
- StagesDropdown,
- JobsContainer,
+export default {
+ name: 'JobSidebar',
+ components: {
+ ArtifactsBlock,
+ CommitBlock,
+ DetailRow,
+ Icon,
+ TriggerBlock,
+ StagesDropdown,
+ JobsContainer,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ runnerHelpUrl: {
+ type: String,
+ required: false,
+ default: '',
},
- mixins: [timeagoMixin],
- props: {
- runnerHelpUrl: {
- type: String,
- required: false,
- default: '',
- },
- terminalPath: {
- type: String,
- required: false,
- default: null,
- },
+ terminalPath: {
+ type: String,
+ required: false,
+ default: null,
},
- computed: {
- ...mapState(['job', 'isLoading', 'stages', 'jobs']),
- coverage() {
- return `${this.job.coverage}%`;
- },
- duration() {
- return timeIntervalInWords(this.job.duration);
- },
- queued() {
- return timeIntervalInWords(this.job.queued);
- },
- runnerId() {
- return `${this.job.runner.description} (#${this.job.runner.id})`;
- },
- retryButtonClass() {
- let className =
- 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
- className +=
- this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
- return className;
- },
- hasTimeout() {
- return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
- },
- timeout() {
- if (this.job.metadata == null) {
- return '';
- }
+ },
+ computed: {
+ ...mapState(['job', 'isLoading', 'stages', 'jobs', 'selectedStage']),
+ coverage() {
+ return `${this.job.coverage}%`;
+ },
+ duration() {
+ return timeIntervalInWords(this.job.duration);
+ },
+ queued() {
+ return timeIntervalInWords(this.job.queued);
+ },
+ runnerId() {
+ return `${this.job.runner.description} (#${this.job.runner.id})`;
+ },
+ retryButtonClass() {
+ let className =
+ 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
+ className +=
+ this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
+ return className;
+ },
+ hasTimeout() {
+ return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
+ },
+ timeout() {
+ if (this.job.metadata == null) {
+ return '';
+ }
- let t = this.job.metadata.timeout_human_readable;
- if (this.job.metadata.timeout_source !== '') {
- t += ` (from ${this.job.metadata.timeout_source})`;
- }
+ let t = this.job.metadata.timeout_human_readable;
+ if (this.job.metadata.timeout_source !== '') {
+ t += ` (from ${this.job.metadata.timeout_source})`;
+ }
- return t;
- },
- renderBlock() {
- return (
- this.job.merge_request ||
- this.job.duration ||
- this.job.finished_data ||
- this.job.erased_at ||
- this.job.queued ||
- this.job.runner ||
- this.job.coverage ||
- this.job.tags.length ||
- this.job.cancel_path
- );
- },
- hasArtifact() {
- return !_.isEmpty(this.job.artifact);
- },
- hasTriggers() {
- return !_.isEmpty(this.job.trigger);
- },
- hasStages() {
- return (
- (this.job &&
- this.job.pipeline &&
- this.job.pipeline.stages &&
- this.job.pipeline.stages.length > 0) ||
- false
- );
- },
- commit() {
- return this.job.pipeline.commit || {};
- },
+ return t;
+ },
+ renderBlock() {
+ return (
+ this.job.merge_request ||
+ this.job.duration ||
+ this.job.finished_data ||
+ this.job.erased_at ||
+ this.job.queued ||
+ this.job.runner ||
+ this.job.coverage ||
+ this.job.tags.length ||
+ this.job.cancel_path
+ );
+ },
+ hasArtifact() {
+ return !_.isEmpty(this.job.artifact);
+ },
+ hasTriggers() {
+ return !_.isEmpty(this.job.trigger);
+ },
+ hasStages() {
+ return (
+ (this.job &&
+ this.job.pipeline &&
+ this.job.pipeline.stages &&
+ this.job.pipeline.stages.length > 0) ||
+ false
+ );
},
- methods: {
- ...mapActions(['fetchJobsForStage']),
+ commit() {
+ return this.job.pipeline.commit || {};
},
- };
+ },
+ methods: {
+ ...mapActions(['fetchJobsForStage']),
+ },
+};
</script>
<template>
<aside
- class="right-sidebar right-sidebar-expanded build-sidebar"
+ class="js-build-sidebar right-sidebar right-sidebar-expanded build-sidebar"
data-offset-top="101"
data-spy="affix"
>
@@ -276,6 +276,7 @@
<stages-dropdown
:stages="stages"
:pipeline="job.pipeline"
+ :selected-stage="selectedStage"
@requestSidebarStageDropdown="fetchJobsForStage"
/>
diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
index 83560a8ff0e..aeafe98a70b 100644
--- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
@@ -1,31 +1,31 @@
<script>
- export default {
- name: 'SidebarDetailRow',
- props: {
- title: {
- type: String,
- required: false,
- default: '',
- },
- value: {
- type: String,
- required: true,
- },
- helpUrl: {
- type: String,
- required: false,
- default: '',
- },
+export default {
+ name: 'SidebarDetailRow',
+ props: {
+ title: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- hasTitle() {
- return this.title.length > 0;
- },
- hasHelpURL() {
- return this.helpUrl.length > 0;
- },
+ value: {
+ type: String,
+ required: true,
},
- };
+ helpUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ hasTitle() {
+ return this.title.length > 0;
+ },
+ hasHelpURL() {
+ return this.helpUrl.length > 0;
+ },
+ },
+};
</script>
<template>
<p class="build-detail-row">
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index 1c15af55a8b..e5e1d56e287 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -1,50 +1,39 @@
<script>
- import _ from 'underscore';
- import CiIcon from '~/vue_shared/components/ci_icon.vue';
- import Icon from '~/vue_shared/components/icon.vue';
- import { __ } from '~/locale';
+import _ from 'underscore';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
- export default {
- components: {
- CiIcon,
- Icon,
+export default {
+ components: {
+ CiIcon,
+ Icon,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
},
- props: {
- pipeline: {
- type: Object,
- required: true,
- },
- stages: {
- type: Array,
- required: true,
- },
+ stages: {
+ type: Array,
+ required: true,
},
- data() {
- return {
- selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'),
- };
+ selectedStage: {
+ type: String,
+ required: true,
},
- computed: {
- hasRef() {
- return !_.isEmpty(this.pipeline.ref);
- },
- },
- watch: {
- // When the component is initially mounted it may start with an empty stages array.
- // Once the prop is updated, we set the first stage as the selected one
- stages(newVal) {
- if (newVal.length) {
- this.selectedStage = newVal[0].name;
- }
- },
+ },
+
+ computed: {
+ hasRef() {
+ return !_.isEmpty(this.pipeline.ref);
},
- methods: {
- onStageClick(stage) {
- this.$emit('requestSidebarStageDropdown', stage);
- this.selectedStage = stage.name;
- },
+ },
+ methods: {
+ onStageClick(stage) {
+ this.$emit('requestSidebarStageDropdown', stage);
},
- };
+ },
+};
</script>
<template>
<div class="block-last dropdown">
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index d7b3c4fcb5b..41de4a6e85a 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -1,27 +1,27 @@
<script>
- export default {
- props: {
- trigger: {
- type: Object,
- required: true,
- },
+export default {
+ props: {
+ trigger: {
+ type: Object,
+ required: true,
},
- data() {
- return {
- areVariablesVisible: false,
- };
+ },
+ data() {
+ return {
+ areVariablesVisible: false,
+ };
+ },
+ computed: {
+ hasVariables() {
+ return this.trigger.variables && this.trigger.variables.length > 0;
},
- computed: {
- hasVariables() {
- return this.trigger.variables && this.trigger.variables.length > 0;
- },
+ },
+ methods: {
+ revealVariables() {
+ this.areVariablesVisible = true;
},
- methods: {
- revealVariables() {
- this.areVariablesVisible = true;
- },
- },
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
index 3eb75e72506..15cd79b1c50 100644
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ b/app/assets/javascripts/jobs/job_details_bundle.js
@@ -9,8 +9,7 @@ import createStore from './store';
export default () => {
const { dataset } = document.getElementById('js-job-details-vue');
- // eslint-disable-next-line no-new
- new Job();
+
const store = createStore();
store.dispatch('setJobEndpoint', dataset.endpoint);
@@ -33,7 +32,7 @@ export default () => {
props: {
isLoading: this.isLoading,
job: this.job,
- runnerHelpUrl: dataset.runnerHelpUrl,
+ runnerSettingsUrl: dataset.runnerSettingsUrl,
},
});
},
@@ -71,4 +70,7 @@ export default () => {
});
},
});
+
+ // eslint-disable-next-line no-new
+ new Job();
};
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 298367c9342..d0040161dc3 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -139,10 +139,12 @@ export const fetchStages = ({ state, dispatch }) => {
dispatch('requestStages');
axios
- .get(state.job.pipeline.path)
+ .get(`${state.job.pipeline.path}.json`)
.then(({ data }) => {
+ // Set selected stage
dispatch('receiveStagesSuccess', data.details.stages);
- dispatch('fetchJobsForStage', data.details.stages[0]);
+ const selectedStage = data.details.stages.find(stage => stage.name === state.selectedStage);
+ dispatch('fetchJobsForStage', selectedStage);
})
.catch(() => dispatch('receiveStagesError'));
};
@@ -156,11 +158,12 @@ export const receiveStagesError = ({ commit }) => {
/**
* Jobs list on sidebar - depend on stages dropdown
*/
-export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE);
+export const requestJobsForStage = ({ commit }, stage) =>
+ commit(types.REQUEST_JOBS_FOR_STAGE, stage);
// On stage click, set selected stage + fetch job
export const fetchJobsForStage = ({ dispatch }, stage) => {
- dispatch('requestJobsForStage');
+ dispatch('requestJobsForStage', stage);
axios
.get(stage.dropdown_path, {
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index afe5f88b292..9f4f372e3d2 100644
--- a/app/assets/javascripts/jobs/store/getters.js
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -22,10 +22,10 @@ export const shouldRenderCalloutMessage = state =>
!_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message);
/**
- * When job has not started the key will be `false`
+ * When job has not started the key will be null
* When job started the key will be a string with a date.
*/
-export const jobHasStarted = state => !(state.job.started === false);
+export const shouldRenderTriggeredLabel = state => _.isString(state.job.started);
export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
diff --git a/app/assets/javascripts/jobs/store/index.js b/app/assets/javascripts/jobs/store/index.js
index 96e38f9a2fa..bba01426af7 100644
--- a/app/assets/javascripts/jobs/store/index.js
+++ b/app/assets/javascripts/jobs/store/index.js
@@ -7,9 +7,10 @@ import mutations from './mutations';
Vue.use(Vuex);
-export default () => new Vuex.Store({
- actions,
- mutations,
- getters,
- state: state(),
-});
+export default () =>
+ new Vuex.Store({
+ actions,
+ mutations,
+ getters,
+ state: state(),
+ });
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index c3f2359fa4d..f00e06e1a6c 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -53,6 +53,16 @@ export default {
state.isLoading = false;
state.hasError = false;
state.job = job;
+
+ /**
+ * We only update it on the first request
+ * The dropdown can be changed by the user
+ * after the first request,
+ * and we do not want to hijack that
+ */
+ if (state.selectedStage === 'More' && job.stage) {
+ state.selectedStage = job.stage;
+ }
},
[types.RECEIVE_JOB_ERROR](state) {
state.isLoading = false;
@@ -81,8 +91,9 @@ export default {
state.stages = [];
},
- [types.REQUEST_JOBS_FOR_STAGE](state) {
+ [types.REQUEST_JOBS_FOR_STAGE](state, stage) {
state.isLoadingJobs = true;
+ state.selectedStage = stage.name;
},
[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](state, jobs) {
state.isLoadingJobs = false;
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
index 509cb69a5d3..afbc959bb71 100644
--- a/app/assets/javascripts/jobs/store/state.js
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export default () => ({
jobEndpoint: null,
traceEndpoint: null,
@@ -34,7 +36,7 @@ export default () => ({
// sidebar dropdown
isLoadingStages: false,
isLoadingJobs: false,
- selectedStage: null,
+ selectedStage: __('More'),
stages: [],
jobs: [],
});
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 68ae1ca6842..3c38d998b6c 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -11,6 +11,7 @@ import DropdownUtils from './filtered_search/dropdown_utils';
import CreateLabelDropdown from './create_label';
import flash from './flash';
import ModalStore from './boards/stores/modal_store';
+import boardsStore from './boards/stores/boards_store';
export default class LabelsSelect {
constructor(els, options = {}) {
@@ -378,7 +379,7 @@ export default class LabelsSelect {
}
else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if ($el.hasClass('is-active')) {
- gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({
+ boardsStore.detail.issue.labels.push(new ListLabel({
id: label.id,
title: label.title,
color: label.color[0],
@@ -386,16 +387,16 @@ export default class LabelsSelect {
}));
}
else {
- var { labels } = gl.issueBoards.BoardsStore.detail.issue;
+ var { labels } = boardsStore.detail.issue;
labels = labels.filter(function (selectedLabel) {
return selectedLabel.id !== label.id;
});
- gl.issueBoards.BoardsStore.detail.issue.labels = labels;
+ boardsStore.detail.issue.labels = labels;
}
$loading.fadeIn();
- gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
+ boardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
.then(fadeOutLoader)
.catch(fadeOutLoader);
}
diff --git a/app/assets/javascripts/lib/ace.js b/app/assets/javascripts/lib/ace.js
index 9cdc0309503..e90b3d2eec7 100644
--- a/app/assets/javascripts/lib/ace.js
+++ b/app/assets/javascripts/lib/ace.js
@@ -1,3 +1,4 @@
/*= require ace/ace */
+/*= require ace/ext-modelist */
/*= require ace/ext-searchbox */
/*= require ./ace/ace_config_paths */
diff --git a/app/assets/javascripts/lib/utils/ace_utils.js b/app/assets/javascripts/lib/utils/ace_utils.js
new file mode 100644
index 00000000000..efc4b2a8d94
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/ace_utils.js
@@ -0,0 +1,6 @@
+/* global ace */
+
+export default function getModeByFileExtension(path) {
+ const modelist = ace.require("ace/ext/modelist");
+ return modelist.getModeForPath(path).mode;
+};
diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js
index 616d8952ada..2d976dbdbbe 100644
--- a/app/assets/javascripts/lib/utils/ajax_cache.js
+++ b/app/assets/javascripts/lib/utils/ajax_cache.js
@@ -4,7 +4,7 @@ import Cache from './cache';
class AjaxCache extends Cache {
constructor() {
super();
- this.pendingRequests = { };
+ this.pendingRequests = {};
}
override(endpoint, data) {
@@ -19,12 +19,13 @@ class AjaxCache extends Cache {
let pendingRequest = this.pendingRequests[endpoint];
if (!pendingRequest) {
- pendingRequest = axios.get(endpoint)
+ pendingRequest = axios
+ .get(endpoint)
.then(({ data }) => {
this.internalStorage[endpoint] = data;
delete this.pendingRequests[endpoint];
})
- .catch((e) => {
+ .catch(e => {
const error = new Error(`${endpoint}: ${e.message}`);
error.textStatus = e.message;
diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js
index 792871e2ecf..69159e2d741 100644
--- a/app/assets/javascripts/lib/utils/axios_utils.js
+++ b/app/assets/javascripts/lib/utils/axios_utils.js
@@ -7,7 +7,7 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// Maintain a global counter for active requests
// see: spec/support/wait_for_requests.rb
-axios.interceptors.request.use((config) => {
+axios.interceptors.request.use(config => {
window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1;
@@ -15,15 +15,18 @@ axios.interceptors.request.use((config) => {
});
// Remove the global counter
-axios.interceptors.response.use((config) => {
- window.activeVueResources -= 1;
-
- return config;
-}, (e) => {
- window.activeVueResources -= 1;
-
- return Promise.reject(e);
-});
+axios.interceptors.response.use(
+ config => {
+ window.activeVueResources -= 1;
+
+ return config;
+ },
+ e => {
+ window.activeVueResources -= 1;
+
+ return Promise.reject(e);
+ },
+);
export default axios;
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
index c28ed04f94f..a24c71aeab1 100644
--- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
@@ -93,9 +93,13 @@ export default class LinkedTabs {
const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
- window.history.replaceState({
- url: newState,
- }, document.title, newState);
+ window.history.replaceState(
+ {
+ url: newState,
+ },
+ document.title,
+ newState,
+ );
return newState;
}
diff --git a/app/assets/javascripts/lib/utils/cache.js b/app/assets/javascripts/lib/utils/cache.js
index 596bd1e388a..45048145139 100644
--- a/app/assets/javascripts/lib/utils/cache.js
+++ b/app/assets/javascripts/lib/utils/cache.js
@@ -1,6 +1,6 @@
export default class Cache {
constructor() {
- this.internalStorage = { };
+ this.internalStorage = {};
}
get(key) {
diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js
index e98c9068367..19e4085dbbb 100644
--- a/app/assets/javascripts/lib/utils/datefix.js
+++ b/app/assets/javascripts/lib/utils/datefix.js
@@ -1,12 +1,11 @@
-
-export const pad = (val, len = 2) => (`0${val}`).slice(-len);
+export const pad = (val, len = 2) => `0${val}`.slice(-len);
/**
* Formats dates in Pickaday
* @param {String} dateString Date in yyyy-mm-dd format
* @return {Date} UTC format
*/
-export const parsePikadayDate = (dateString) => {
+export const parsePikadayDate = dateString => {
const parts = dateString.split('-');
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1] - 1, 10);
@@ -20,7 +19,7 @@ export const parsePikadayDate = (dateString) => {
* @param {Date} date UTC format
* @return {String} Date formated in yyyy-mm-dd
*/
-export const pikadayToString = (date) => {
+export const pikadayToString = date => {
const day = pad(date.getDate());
const month = pad(date.getMonth() + 1);
const year = date.getFullYear();
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index f4eb652a41a..d93873e0214 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -8,7 +8,7 @@ function notificationGranted(message, opts, onclick) {
return notification.close();
}, 8000);
- return notification.onclick = onclick || notification.close;
+ return (notification.onclick = onclick || notification.close);
}
function notifyPermissions() {
@@ -21,7 +21,7 @@ function notifyMe(message, body, icon, onclick) {
var opts;
opts = {
body: body,
- icon: icon
+ icon: icon,
};
// Let's check if the browser supports notifications
if (!('Notification' in window)) {
diff --git a/app/assets/javascripts/lib/utils/pretty_time.js b/app/assets/javascripts/lib/utils/pretty_time.js
index b1ffd797f7e..d92b8a7179f 100644
--- a/app/assets/javascripts/lib/utils/pretty_time.js
+++ b/app/assets/javascripts/lib/utils/pretty_time.js
@@ -27,10 +27,10 @@ export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {})
let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR);
- return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => {
+ return _.mapObject(timePeriodConstraints, minutesPerPeriod => {
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
- unorderedMinutes -= (periodCount * minutesPerPeriod);
+ unorderedMinutes -= periodCount * minutesPerPeriod;
return periodCount;
});
@@ -42,10 +42,14 @@ export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {})
*/
export function stringifyTime(timeObject) {
- const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => {
- const isNonZero = !!unitValue;
- return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
- }, '').trim();
+ const reducedTime = _.reduce(
+ timeObject,
+ (memo, unitValue, unitName) => {
+ const isNonZero = !!unitValue;
+ return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
+ },
+ '',
+ ).trim();
return reducedTime.length ? reducedTime : '0m';
}
@@ -55,7 +59,5 @@ export function stringifyTime(timeObject) {
*/
export function abbreviateTime(timeStr) {
- return timeStr.split(' ')
- .filter(unitStr => unitStr.charAt(0) !== '0')[0];
+ return timeStr.split(' ').filter(unitStr => unitStr.charAt(0) !== '0')[0];
}
-
diff --git a/app/assets/javascripts/lib/utils/regexp.js b/app/assets/javascripts/lib/utils/regexp.js
index baa0b51d59b..25b60dcd14a 100644
--- a/app/assets/javascripts/lib/utils/regexp.js
+++ b/app/assets/javascripts/lib/utils/regexp.js
@@ -5,6 +5,7 @@
// Inspired by https://github.com/mishoo/UglifyJS/blob/2bc1d02363db3798d5df41fb5059a19edca9b7eb/lib/parse-js.js#L203
// Unicode 6.1
-const unicodeLetters = '\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC';
+const unicodeLetters =
+ '\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC';
export default { unicodeLetters };
diff --git a/app/assets/javascripts/lib/utils/simple_poll.js b/app/assets/javascripts/lib/utils/simple_poll.js
index 25ca98afbe7..473f179ad86 100644
--- a/app/assets/javascripts/lib/utils/simple_poll.js
+++ b/app/assets/javascripts/lib/utils/simple_poll.js
@@ -2,7 +2,7 @@ export default (fn, interval = 2000, timeout = 60000) => {
const startTime = Date.now();
return new Promise((resolve, reject) => {
- const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
+ const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
const next = () => {
if (Date.now() - startTime < timeout) {
setTimeout(fn.bind(null, next, stop), interval);
diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js
index 15a4dd62012..f3244301350 100644
--- a/app/assets/javascripts/lib/utils/sticky.js
+++ b/app/assets/javascripts/lib/utils/sticky.js
@@ -24,7 +24,11 @@ export const isSticky = (el, scrollY, stickyTop, insertPlaceholder) => {
} else if (top > stickyTop && el.classList.contains('is-stuck')) {
el.classList.remove('is-stuck');
- if (insertPlaceholder && el.nextElementSibling && el.nextElementSibling.classList.contains('sticky-placeholder')) {
+ if (
+ insertPlaceholder &&
+ el.nextElementSibling &&
+ el.nextElementSibling.classList.contains('sticky-placeholder')
+ ) {
el.nextElementSibling.remove();
}
}
@@ -42,11 +46,19 @@ export const isSticky = (el, scrollY, stickyTop, insertPlaceholder) => {
export const stickyMonitor = (el, stickyTop, insertPlaceholder = true) => {
if (!el) return;
- if (typeof CSS === 'undefined' || !(CSS.supports('(position: -webkit-sticky) or (position: sticky)'))) return;
+ if (
+ typeof CSS === 'undefined' ||
+ !CSS.supports('(position: -webkit-sticky) or (position: sticky)')
+ )
+ return;
- document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop, insertPlaceholder), {
- passive: true,
- });
+ document.addEventListener(
+ 'scroll',
+ () => isSticky(el, window.scrollY, stickyTop, insertPlaceholder),
+ {
+ passive: true,
+ },
+ );
};
/**
@@ -55,6 +67,6 @@ export const stickyMonitor = (el, stickyTop, insertPlaceholder = true) => {
* - If the current environment supports `position: sticky`, do nothing.
* - Can receive an iterable element list (NodeList, jQuery collection, etc.) or single HTMLElement.
*/
-export const polyfillSticky = (el) => {
+export const polyfillSticky = el => {
StickyFill.add(el);
};
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index df20785b178..e26a6b986be 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -8,12 +8,18 @@ function selectedText(text, textarea) {
function lineBefore(text, textarea) {
var split;
- split = text.substring(0, textarea.selectionStart).trim().split('\n');
+ split = text
+ .substring(0, textarea.selectionStart)
+ .trim()
+ .split('\n');
return split[split.length - 1];
}
function lineAfter(text, textarea) {
- return text.substring(textarea.selectionEnd).trim().split('\n')[0];
+ return text
+ .substring(textarea.selectionEnd)
+ .trim()
+ .split('\n')[0];
}
function blockTagText(text, textArea, blockTag, selected) {
@@ -27,7 +33,7 @@ function blockTagText(text, textArea, blockTag, selected) {
}
return selected;
} else {
- return blockTag + "\n" + selected + "\n" + blockTag;
+ return blockTag + '\n' + selected + '\n' + blockTag;
}
}
@@ -58,7 +64,14 @@ function moveCursor({ textArea, tag, wrapped, removedLastNewLine, select }) {
}
export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select }) {
- var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
+ var textToInsert,
+ inserted,
+ selectedSplit,
+ startChar,
+ removedLastNewLine,
+ removedFirstNewLine,
+ currentLineEmpty,
+ lastNewLine;
removedLastNewLine = false;
removedFirstNewLine = false;
currentLineEmpty = false;
@@ -94,21 +107,23 @@ export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wr
if (blockTag != null && blockTag !== '') {
textToInsert = blockTagText(text, textArea, blockTag, selected);
} else {
- textToInsert = selectedSplit.map(function(val) {
- if (tag.indexOf(textPlaceholder) > -1) {
- return tag.replace(textPlaceholder, val);
- }
- if (val.indexOf(tag) === 0) {
- return "" + (val.replace(tag, ''));
- } else {
- return "" + tag + val;
- }
- }).join('\n');
+ textToInsert = selectedSplit
+ .map(function(val) {
+ if (tag.indexOf(textPlaceholder) > -1) {
+ return tag.replace(textPlaceholder, val);
+ }
+ if (val.indexOf(tag) === 0) {
+ return '' + val.replace(tag, '');
+ } else {
+ return '' + tag + val;
+ }
+ })
+ .join('\n');
}
} else if (tag.indexOf(textPlaceholder) > -1) {
textToInsert = tag.replace(textPlaceholder, selected);
} else {
- textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' ');
+ textToInsert = '' + startChar + tag + selected + (wrap ? tag : ' ');
}
if (removedFirstNewLine) {
@@ -120,7 +135,13 @@ export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wr
}
insertText(textArea, textToInsert);
- return moveCursor({ textArea, tag: tag.replace(textPlaceholder, selected), wrap, removedLastNewLine, select });
+ return moveCursor({
+ textArea,
+ tag: tag.replace(textPlaceholder, selected),
+ wrap,
+ removedLastNewLine,
+ select,
+ });
}
function updateText({ textArea, tag, blockTag, wrap, select }) {
@@ -138,15 +159,18 @@ function replaceRange(s, start, end, substitute) {
}
export function addMarkdownListeners(form) {
- return $('.js-md', form).off('click').on('click', function() {
- const $this = $(this);
- return updateText({
- textArea: $this.closest('.md-area').find('textarea'),
- tag: $this.data('mdTag'),
- blockTag: $this.data('mdBlock'),
- wrap: !$this.data('mdPrepend'),
- select: $this.data('mdSelect') });
- });
+ return $('.js-md', form)
+ .off('click')
+ .on('click', function() {
+ const $this = $(this);
+ return updateText({
+ textArea: $this.closest('.md-area').find('textarea'),
+ tag: $this.data('mdTag'),
+ blockTag: $this.data('mdBlock'),
+ wrap: !$this.data('mdPrepend'),
+ select: $this.data('mdSelect'),
+ });
+ });
}
export function removeMarkdownListeners(form) {
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 879f94a26ec..7cc7cd6d20e 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -8,7 +8,7 @@
* @returns {String}
*/
export const addDelimiter = text =>
- (text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text);
+ text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text;
/**
* Returns '99+' for numbers bigger than 99.
@@ -53,10 +53,7 @@ export const slugify = str => str.trim().toLowerCase();
* @param {String} str
* @returns {String}
*/
-export const slugifyWithHyphens = str => {
- const regex = new RegExp(/\s+/, 'g');
- return str.toLowerCase().replace(regex, '-');
-};
+export const slugifyWithHyphens = str => str.toLowerCase().replace(/\s+/g, '-');
/**
* Truncates given text
@@ -94,9 +91,7 @@ export function capitalizeFirstCharacter(text) {
* @return {String}
*/
export function getFirstCharacterCapitalized(text) {
- return text
- ? text.charAt(0).toUpperCase()
- : '';
+ return text ? text.charAt(0).toUpperCase() : '';
}
/**
@@ -136,10 +131,9 @@ export const convertToSentenceCase = string => {
* e.g. HelloWorld => Hello World
*
* @param {*} string
-*/
-export const splitCamelCase = string => (
+ */
+export const splitCamelCase = string =>
string
- .replace(/([A-Z]+)([A-Z][a-z])/g, ' $1 $2')
- .replace(/([a-z\d])([A-Z])/g, '$1 $2')
- .trim()
-);
+ .replace(/([A-Z]+)([A-Z][a-z])/g, ' $1 $2')
+ .replace(/([a-z\d])([A-Z])/g, '$1 $2')
+ .trim();
diff --git a/app/assets/javascripts/lib/utils/tick_formats.js b/app/assets/javascripts/lib/utils/tick_formats.js
index 0c10a85e336..af3ca714400 100644
--- a/app/assets/javascripts/lib/utils/tick_formats.js
+++ b/app/assets/javascripts/lib/utils/tick_formats.js
@@ -26,7 +26,7 @@ initDateFormats();
see also https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickFormat
*/
-export const dateTickFormat = (date) => {
+export const dateTickFormat = date => {
if (date.getDate() !== 1) {
return dateTimeFormats.dayFormat.format(date);
}
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
index b01ec6b81a3..c0d45e017b4 100644
--- a/app/assets/javascripts/lib/utils/users_cache.js
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -7,21 +7,20 @@ class UsersCache extends Cache {
return Promise.resolve(this.get(username));
}
- return Api.users('', { username })
- .then(({ data }) => {
- if (!data.length) {
- throw new Error(`User "${username}" could not be found!`);
- }
+ return Api.users('', { username }).then(({ data }) => {
+ if (!data.length) {
+ throw new Error(`User "${username}" could not be found!`);
+ }
- if (data.length > 1) {
- throw new Error(`Expected username "${username}" to be unique!`);
- }
+ if (data.length > 1) {
+ throw new Error(`Expected username "${username}" to be unique!`);
+ }
- const user = data[0];
- this.internalStorage[username] = user;
- return user;
- });
- // missing catch is intentional, error handling depends on use case
+ const user = data[0];
+ this.internalStorage[username] = user;
+ return user;
+ });
+ // missing catch is intentional, error handling depends on use case
}
}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index e8aac51a299..a88b575ad99 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -202,7 +202,6 @@ document.addEventListener('DOMContentLoaded', () => {
$('.navbar-toggler').on('click', () => {
$('.header-content').toggleClass('menu-expanded');
- gl.lazyLoader.loadCheck();
});
// Show/hide comments on diff
diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
index c1832d034ef..a62ebe23646 100644
--- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
+++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
@@ -5,8 +5,9 @@ import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
+import getModeByFileExtension from '~/lib/utils/ace_utils';
-((global) => {
+(global => {
global.mergeConflicts = global.mergeConflicts || {};
global.mergeConflicts.diffFileEditor = Vue.extend({
@@ -35,10 +36,10 @@ import { __ } from '~/locale';
computed: {
classObject() {
return {
- 'saved': this.saved,
- 'is-loading': this.loading
+ saved: this.saved,
+ 'is-loading': this.loading,
};
- }
+ },
},
watch: {
['file.showEditor'](val) {
@@ -49,7 +50,7 @@ import { __ } from '~/locale';
}
this.loadEditor();
- }
+ },
},
mounted() {
if (this.file.loadEditor) {
@@ -60,7 +61,8 @@ import { __ } from '~/locale';
loadEditor() {
this.loading = true;
- axios.get(this.file.content_path)
+ axios
+ .get(this.file.content_path)
.then(({ data }) => {
const content = this.$el.querySelector('pre');
const fileContent = document.createTextNode(data.content);
@@ -71,7 +73,7 @@ import { __ } from '~/locale';
this.fileLoaded = true;
this.editor = ace.edit(content);
this.editor.$blockScrolling = Infinity; // Turn off annoying warning
- this.editor.getSession().setMode(`ace/mode/${data.blob_ace_mode}`);
+ this.editor.getSession().setMode(getModeByFileExtension(data.new_path));
this.editor.on('change', () => {
this.saveDiffResolution();
});
@@ -101,7 +103,7 @@ import { __ } from '~/locale';
},
acceptDiscardConfirmation(file) {
this.onAcceptDiscardConfirmation(file);
- }
- }
+ },
+ },
});
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
index 69208ac2d36..c2de0379d23 100644
--- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
@@ -4,7 +4,7 @@ import Vue from 'vue';
import actionsMixin from '../mixins/line_conflict_actions';
import utilsMixin from '../mixins/line_conflict_utils';
-((global) => {
+(global => {
global.mergeConflicts = global.mergeConflicts || {};
global.mergeConflicts.parallelConflictLines = Vue.extend({
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index 2cd70247bc6..0333335de06 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
@@ -4,7 +4,7 @@ import $ from 'jquery';
import Vue from 'vue';
import Cookies from 'js-cookie';
-((global) => {
+(global => {
global.mergeConflicts = global.mergeConflicts || {};
const diffViewType = Cookies.get('diff_view');
@@ -17,11 +17,11 @@ import Cookies from 'js-cookie';
const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
const VIEW_TYPES = {
INLINE: 'inline',
- PARALLEL: 'parallel'
+ PARALLEL: 'parallel',
};
const CONFLICT_TYPES = {
TEXT: 'text',
- TEXT_EDITOR: 'text-editor'
+ TEXT_EDITOR: 'text-editor',
};
global.mergeConflicts.mergeConflictsStore = {
@@ -31,7 +31,7 @@ import Cookies from 'js-cookie';
isSubmitting: false,
isParallel: diffViewType === VIEW_TYPES.PARALLEL,
diffViewType: diffViewType,
- conflictsData: {}
+ conflictsData: {},
},
setConflictsData(data) {
@@ -47,7 +47,7 @@ import Cookies from 'js-cookie';
},
decorateFiles(files) {
- files.forEach((file) => {
+ files.forEach(file => {
file.content = '';
file.resolutionData = {};
file.promptDiscardConfirmation = false;
@@ -72,7 +72,7 @@ import Cookies from 'js-cookie';
setInlineLine(file) {
file.inlineLines = [];
- file.sections.forEach((section) => {
+ file.sections.forEach(section => {
let currentLineType = 'new';
const { conflict, lines, id } = section;
@@ -80,7 +80,7 @@ import Cookies from 'js-cookie';
file.inlineLines.push(this.getHeadHeaderLine(id));
}
- lines.forEach((line) => {
+ lines.forEach(line => {
const { type } = line;
if ((type === 'new' || type === 'old') && currentLineType !== type) {
@@ -102,7 +102,7 @@ import Cookies from 'js-cookie';
file.parallelLines = [];
const linesObj = { left: [], right: [] };
- file.sections.forEach((section) => {
+ file.sections.forEach(section => {
const { conflict, lines, id } = section;
if (conflict) {
@@ -110,7 +110,7 @@ import Cookies from 'js-cookie';
linesObj.right.push(this.getHeadHeaderLine(id));
}
- lines.forEach((line) => {
+ lines.forEach(line => {
const { type } = line;
if (conflict) {
@@ -131,10 +131,7 @@ import Cookies from 'js-cookie';
});
for (let i = 0, len = linesObj.left.length; i < len; i += 1) {
- file.parallelLines.push([
- linesObj.right[i],
- linesObj.left[i]
- ]);
+ file.parallelLines.push([linesObj.right[i], linesObj.left[i]]);
}
},
@@ -159,9 +156,9 @@ import Cookies from 'js-cookie';
const { files } = this.state.conflictsData;
let count = 0;
- files.forEach((file) => {
+ files.forEach(file => {
if (file.type === CONFLICT_TYPES.TEXT) {
- file.sections.forEach((section) => {
+ file.sections.forEach(section => {
if (section.conflict) {
count += 1;
}
@@ -198,7 +195,7 @@ import Cookies from 'js-cookie';
isHeader: true,
isHead: true,
isSelected: false,
- isUnselected: false
+ isUnselected: false,
};
},
@@ -229,7 +226,7 @@ import Cookies from 'js-cookie';
section: isHead ? 'head' : 'origin',
richText: rich_text,
isSelected: false,
- isUnselected: false
+ isUnselected: false,
};
},
@@ -243,7 +240,7 @@ import Cookies from 'js-cookie';
isHeader: true,
isOrigin: true,
isSelected: false,
- isUnselected: false
+ isUnselected: false,
};
},
@@ -290,14 +287,14 @@ import Cookies from 'js-cookie';
},
restoreFileLinesState(file) {
- file.inlineLines.forEach((line) => {
+ file.inlineLines.forEach(line => {
if (line.hasConflict || line.isHeader) {
line.isSelected = false;
line.isUnselected = false;
}
});
- file.parallelLines.forEach((lines) => {
+ file.parallelLines.forEach(lines => {
const left = lines[0];
const right = lines[1];
const isLeftMatch = left.hasConflict || left.isHeader;
@@ -354,7 +351,7 @@ import Cookies from 'js-cookie';
const initial = 'Commit to source branch';
const inProgress = 'Committing...';
- return this.state ? this.state.isSubmitting ? inProgress : initial : initial;
+ return this.state ? (this.state.isSubmitting ? inProgress : initial) : initial;
},
getCommitData() {
@@ -362,13 +359,13 @@ import Cookies from 'js-cookie';
commitData = {
commit_message: this.state.conflictsData.commitMessage,
- files: []
+ files: [],
};
- this.state.conflictsData.files.forEach((file) => {
+ this.state.conflictsData.files.forEach(file => {
const addFile = {
old_path: file.old_path,
- new_path: file.new_path
+ new_path: file.new_path,
};
if (file.type === CONFLICT_TYPES.TEXT) {
@@ -391,13 +388,13 @@ import Cookies from 'js-cookie';
handleSelected(file, sectionId, selection) {
Vue.set(file.resolutionData, sectionId, selection);
- file.inlineLines.forEach((line) => {
+ file.inlineLines.forEach(line => {
if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
this.markLine(line, selection);
}
});
- file.parallelLines.forEach((lines) => {
+ file.parallelLines.forEach(lines => {
const left = lines[0];
const right = lines[1];
const hasSameId = right.id === sectionId || left.id === sectionId;
@@ -430,6 +427,6 @@ import Cookies from 'js-cookie';
fileTextTypePresent() {
return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT);
- }
+ },
};
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 67c2d7909a2..42fb5c7177a 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -9,6 +9,7 @@ import '~/gl_dropdown';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
import ModalStore from './boards/stores/modal_store';
+import boardsStore, { boardStoreIssueSet, boardStoreIssueDelete } from './boards/stores/boards_store';
export default class MilestoneSelect {
constructor(currentProject, els, options = {}) {
@@ -187,7 +188,7 @@ export default class MilestoneSelect {
return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (selected.id !== -1 && isSelecting) {
- gl.issueBoards.boardStoreIssueSet(
+ boardStoreIssueSet(
'milestone',
new ListMilestone({
id: selected.id,
@@ -195,13 +196,13 @@ export default class MilestoneSelect {
}),
);
} else {
- gl.issueBoards.boardStoreIssueDelete('milestone');
+ boardStoreIssueDelete('milestone');
}
$dropdown.trigger('loading.gl.dropdown');
$loading.removeClass('hidden').fadeIn();
- gl.issueBoards.BoardsStore.detail.issue
+ boardsStore.detail.issue
.update($dropdown.attr('data-issue-update'))
.then(() => {
$dropdown.trigger('loaded.gl.dropdown');
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index 3cccaf72ed7..ed5c8b15945 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -148,7 +148,7 @@ export default {
point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
point.x += 7;
- this.seriesUnderMouse = this.timeSeries.filter((series) => {
+ this.seriesUnderMouse = this.timeSeries.filter(series => {
const mouseX = series.timeSeriesScaleX.invert(point.x);
let minDistance = Infinity;
@@ -221,21 +221,18 @@ export default {
.scale(axisYScale)
.ticks(measurements.yTicks);
- d3
- .select(this.$refs.baseSvg)
+ d3.select(this.$refs.baseSvg)
.select('.x-axis')
.call(xAxis);
const width = this.graphWidth;
- d3
- .select(this.$refs.baseSvg)
+ d3.select(this.$refs.baseSvg)
.select('.y-axis')
.call(yAxis)
.selectAll('.tick')
.each(function createTickLines(d, i) {
if (i > 0) {
- d3
- .select(this)
+ d3.select(this)
.select('line')
.attr('x2', width)
.attr('class', 'axis-tick');
diff --git a/app/assets/javascripts/monitoring/components/graph/axis.vue b/app/assets/javascripts/monitoring/components/graph/axis.vue
index 8a604a51eb2..616410ec34f 100644
--- a/app/assets/javascripts/monitoring/components/graph/axis.vue
+++ b/app/assets/javascripts/monitoring/components/graph/axis.vue
@@ -38,38 +38,25 @@ export default {
computed: {
textTransform() {
const yCoordinate =
- (this.graphHeight -
- this.margin.top +
- this.measurements.axisLabelLineOffset) /
- 2 || 0;
+ (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 || 0;
return `translate(15, ${yCoordinate}) rotate(-90)`;
},
rectTransform() {
const yCoordinate =
- (this.graphHeight -
- this.margin.top +
- this.measurements.axisLabelLineOffset) /
- 2 +
+ (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 +
this.yLabelWidth / 2 || 0;
return `translate(0, ${yCoordinate}) rotate(-90)`;
},
xPosition() {
- return (
- (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 -
- this.margin.right || 0
- );
+ return (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - this.margin.right || 0;
},
yPosition() {
- return (
- this.graphHeight -
- this.margin.top +
- this.measurements.axisLabelLineOffset || 0
- );
+ return this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset || 0;
},
yAxisLabelSentenceCase() {
diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue
index 5f00d20ca3f..1720476480e 100644
--- a/app/assets/javascripts/monitoring/components/graph/flag.vue
+++ b/app/assets/javascripts/monitoring/components/graph/flag.vue
@@ -92,7 +92,8 @@ export default {
methods: {
seriesMetricValue(seriesIndex, series) {
const indexFromCoordinates = this.currentCoordinates[series.metricTag]
- ? this.currentCoordinates[series.metricTag].currentDataIndex : 0;
+ ? this.currentCoordinates[series.metricTag].currentDataIndex
+ : 0;
const index = this.deploymentFlagData
? this.deploymentFlagData.seriesIndex
: indexFromCoordinates;
diff --git a/app/assets/javascripts/monitoring/components/graph/track_info.vue b/app/assets/javascripts/monitoring/components/graph/track_info.vue
index ec1c2222af9..3464067834f 100644
--- a/app/assets/javascripts/monitoring/components/graph/track_info.vue
+++ b/app/assets/javascripts/monitoring/components/graph/track_info.vue
@@ -26,4 +26,3 @@ export default {
{{ summaryMetrics }}
</span>
</template>
-
diff --git a/app/assets/javascripts/monitoring/components/graph/track_line.vue b/app/assets/javascripts/monitoring/components/graph/track_line.vue
index ba3f93b39ff..e04fd9c1f35 100644
--- a/app/assets/javascripts/monitoring/components/graph/track_line.vue
+++ b/app/assets/javascripts/monitoring/components/graph/track_line.vue
@@ -33,4 +33,3 @@ export default {
</svg>
</td>
</template>
-
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
index 007451d5c7a..87c3d969de4 100644
--- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -6,7 +6,7 @@ const mixins = {
if (!this.reducedDeploymentData) return false;
let dataFound = false;
- this.reducedDeploymentData = this.reducedDeploymentData.map((d) => {
+ this.reducedDeploymentData = this.reducedDeploymentData.map(d => {
const deployment = d;
if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
dataFound = d.xPos + 1;
@@ -61,7 +61,7 @@ const mixins = {
this.currentCoordinates = {};
- this.seriesUnderMouse.forEach((series) => {
+ this.seriesUnderMouse.forEach(series => {
const currentDataIndex = bisectDate(series.values, this.hoverData.hoveredDate);
const currentData = series.values[currentDataIndex];
const currentX = Math.floor(series.timeSeriesScaleX(currentData.time));
diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js
index 260d424378e..24b4acaf6da 100644
--- a/app/assets/javascripts/monitoring/services/monitoring_service.js
+++ b/app/assets/javascripts/monitoring/services/monitoring_service.js
@@ -8,18 +8,20 @@ const MAX_REQUESTS = 3;
function backOffRequest(makeRequestCallback) {
let requestCounter = 0;
return backOff((next, stop) => {
- makeRequestCallback().then((resp) => {
- if (resp.status === statusCodes.NO_CONTENT) {
- requestCounter += 1;
- if (requestCounter < MAX_REQUESTS) {
- next();
+ makeRequestCallback()
+ .then(resp => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ requestCounter += 1;
+ if (requestCounter < MAX_REQUESTS) {
+ next();
+ } else {
+ stop(new Error('Failed to connect to the prometheus server'));
+ }
} else {
- stop(new Error('Failed to connect to the prometheus server'));
+ stop(resp);
}
- } else {
- stop(resp);
- }
- }).catch(stop);
+ })
+ .catch(stop);
});
}
@@ -33,7 +35,7 @@ export default class MonitoringService {
getGraphsData() {
return backOffRequest(() => axios.get(this.metricsEndpoint))
.then(resp => resp.data)
- .then((response) => {
+ .then(response => {
if (!response || !response.data) {
throw new Error(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
}
@@ -47,22 +49,27 @@ export default class MonitoringService {
}
return backOffRequest(() => axios.get(this.deploymentEndpoint))
.then(resp => resp.data)
- .then((response) => {
+ .then(response => {
if (!response || !response.deployments) {
- throw new Error(s__('Metrics|Unexpected deployment data response from prometheus endpoint'));
+ throw new Error(
+ s__('Metrics|Unexpected deployment data response from prometheus endpoint'),
+ );
}
return response.deployments;
});
}
getEnvironmentsData() {
- return axios.get(this.environmentsEndpoint)
- .then(resp => resp.data)
- .then((response) => {
- if (!response || !response.environments) {
- throw new Error(s__('Metrics|There was an error fetching the environments data, please try again'));
- }
- return response.environments;
- });
+ return axios
+ .get(this.environmentsEndpoint)
+ .then(resp => resp.data)
+ .then(response => {
+ if (!response || !response.environments) {
+ throw new Error(
+ s__('Metrics|There was an error fetching the environments data, please try again'),
+ );
+ }
+ return response.environments;
+ });
}
}
diff --git a/app/assets/javascripts/monitoring/utils/measurements.js b/app/assets/javascripts/monitoring/utils/measurements.js
index ee866850e13..7c771f43eee 100644
--- a/app/assets/javascripts/monitoring/utils/measurements.js
+++ b/app/assets/javascripts/monitoring/utils/measurements.js
@@ -1,5 +1,6 @@
export default {
- small: { // Covers both xs and sm screen sizes
+ small: {
+ // Covers both xs and sm screen sizes
margin: {
top: 40,
right: 40,
@@ -18,7 +19,8 @@ export default {
},
axisLabelLineOffset: -20,
},
- large: { // This covers both md and lg screen sizes
+ large: {
+ // This covers both md and lg screen sizes
margin: {
top: 80,
right: 80,
diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
index d5971730e31..bb24a1acdb3 100644
--- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js
+++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
@@ -66,7 +66,8 @@ function queryTimeSeries(query, graphDrawData, lineStyle) {
// offset the same amount as the original data
const [minX, maxX] = graphDrawData.xDom;
const offset = d3.timeMinute(minX) - Number(minX);
- const datesWithoutGaps = d3.timeSecond.every(60)
+ const datesWithoutGaps = d3.timeSecond
+ .every(60)
.range(d3.timeMinute.offset(minX, -1), maxX)
.map(d => d - offset);
@@ -208,9 +209,7 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph
const timeSeries = queries.reduce((series, query, index) => {
const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length];
- return series.concat(
- queryTimeSeries(query, graphDrawData, lineStyle),
- );
+ return series.concat(queryTimeSeries(query, graphDrawData, lineStyle));
}, []);
return {
diff --git a/app/assets/javascripts/notebook/cells/code/index.vue b/app/assets/javascripts/notebook/cells/code/index.vue
index 7d2a1a33b98..0691ba64f8e 100644
--- a/app/assets/javascripts/notebook/cells/code/index.vue
+++ b/app/assets/javascripts/notebook/cells/code/index.vue
@@ -1,45 +1,45 @@
<script>
- import Prism from '../../lib/highlight';
- import Prompt from '../prompt.vue';
+import Prism from '../../lib/highlight';
+import Prompt from '../prompt.vue';
- export default {
- components: {
- prompt: Prompt,
+export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
},
- props: {
- count: {
- type: Number,
- required: false,
- default: 0,
- },
- codeCssClass: {
- type: String,
- required: false,
- default: '',
- },
- type: {
- type: String,
- required: true,
- },
- rawCode: {
- type: String,
- required: true,
- },
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- code() {
- return this.rawCode;
- },
- promptType() {
- const type = this.type.split('put')[0];
-
- return type.charAt(0).toUpperCase() + type.slice(1);
- },
+ type: {
+ type: String,
+ required: true,
+ },
+ rawCode: {
+ type: String,
+ required: true,
},
- mounted() {
- Prism.highlightElement(this.$refs.code);
+ },
+ computed: {
+ code() {
+ return this.rawCode;
+ },
+ promptType() {
+ const type = this.type.split('put')[0];
+
+ return type.charAt(0).toUpperCase() + type.slice(1);
},
- };
+ },
+ mounted() {
+ Prism.highlightElement(this.$refs.code);
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index 3d09d24b6ab..5aa83db0986 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -1,12 +1,12 @@
<script>
- /* global katex */
- import marked from 'marked';
- import sanitize from 'sanitize-html';
- import Prompt from './prompt.vue';
+/* global katex */
+import marked from 'marked';
+import sanitize from 'sanitize-html';
+import Prompt from './prompt.vue';
- const renderer = new marked.Renderer();
+const renderer = new marked.Renderer();
- /*
+/*
Regex to match KaTex blocks.
Supports the following:
@@ -17,7 +17,7 @@
The matched text then goes through the KaTex renderer & then outputs the HTML
*/
- const katexRegexString = `(
+const katexRegexString = `(
^\\\\begin{[a-zA-Z]+}\\s
|
^\\$\\$
@@ -32,66 +32,69 @@
|
\\$
)
- `.replace(/\s/g, '').trim();
+ `
+ .replace(/\s/g, '')
+ .trim();
- renderer.paragraph = (t) => {
- let text = t;
- let inline = false;
+renderer.paragraph = t => {
+ let text = t;
+ let inline = false;
- if (typeof katex !== 'undefined') {
- const katexString = text.replace(/&amp;/g, '&')
- .replace(/&=&/g, '\\space=\\space')
- .replace(/<(\/?)em>/g, '_');
- const regex = new RegExp(katexRegexString, 'gi');
- const matchLocation = katexString.search(regex);
- const numberOfMatches = katexString.match(regex);
+ if (typeof katex !== 'undefined') {
+ const katexString = text
+ .replace(/&amp;/g, '&')
+ .replace(/&=&/g, '\\space=\\space')
+ .replace(/<(\/?)em>/g, '_');
+ const regex = new RegExp(katexRegexString, 'gi');
+ const matchLocation = katexString.search(regex);
+ const numberOfMatches = katexString.match(regex);
- if (numberOfMatches && numberOfMatches.length !== 0) {
- if (matchLocation > 0) {
- let matches = regex.exec(katexString);
- inline = true;
+ if (numberOfMatches && numberOfMatches.length !== 0) {
+ if (matchLocation > 0) {
+ let matches = regex.exec(katexString);
+ inline = true;
- while (matches !== null) {
- const renderedKatex = katex.renderToString(matches[0].replace(/\$/g, ''));
- text = `${text.replace(matches[0], ` ${renderedKatex}`)}`;
- matches = regex.exec(katexString);
- }
- } else {
- const matches = regex.exec(katexString);
- text = katex.renderToString(matches[2]);
+ while (matches !== null) {
+ const renderedKatex = katex.renderToString(matches[0].replace(/\$/g, ''));
+ text = `${text.replace(matches[0], ` ${renderedKatex}`)}`;
+ matches = regex.exec(katexString);
}
+ } else {
+ const matches = regex.exec(katexString);
+ text = katex.renderToString(matches[2]);
}
}
+ }
- return `<p class="${inline ? 'inline-katex' : ''}">${text}</p>`;
- };
+ return `<p class="${inline ? 'inline-katex' : ''}">${text}</p>`;
+};
- marked.setOptions({
- sanitize: true,
- renderer,
- });
+marked.setOptions({
+ sanitize: true,
+ renderer,
+});
- export default {
- components: {
- prompt: Prompt,
- },
- props: {
- cell: {
- type: Object,
- required: true,
- },
+export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ cell: {
+ type: Object,
+ required: true,
},
- computed: {
- markdown() {
- return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), {
- allowedTags: false,
- allowedAttributes: {
- '*': ['class'],
- },
- });
- },
+ },
+ computed: {
+ markdown() {
+ return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), {
+ allowedTags: false,
+ allowedAttributes: {
+ '*': ['class'],
+ },
+ });
},
- };
+ },
+};
</script>
<template>
@@ -105,13 +108,13 @@
</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 0535ee7afa8..c6fc786fa76 100644
--- a/app/assets/javascripts/notebook/cells/output/html.vue
+++ b/app/assets/javascripts/notebook/cells/output/html.vue
@@ -1,30 +1,28 @@
<script>
- import sanitize from 'sanitize-html';
- import Prompt from '../prompt.vue';
+import sanitize from 'sanitize-html';
+import Prompt from '../prompt.vue';
- export default {
- components: {
- prompt: Prompt,
+export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ rawCode: {
+ type: String,
+ required: true,
},
- props: {
- rawCode: {
- type: String,
- required: true,
- },
+ },
+ computed: {
+ sanitizedOutput() {
+ return sanitize(this.rawCode, {
+ allowedTags: sanitize.defaults.allowedTags.concat(['img', 'svg']),
+ allowedAttributes: {
+ img: ['src'],
+ },
+ });
},
- computed: {
- sanitizedOutput() {
- return sanitize(this.rawCode, {
- allowedTags: sanitize.defaults.allowedTags.concat([
- 'img', 'svg',
- ]),
- allowedAttributes: {
- img: ['src'],
- },
- });
- },
- },
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue
index 67d6c5ad12b..a17868963ce 100644
--- a/app/assets/javascripts/notebook/cells/output/image.vue
+++ b/app/assets/javascripts/notebook/cells/output/image.vue
@@ -1,21 +1,21 @@
<script>
- import Prompt from '../prompt.vue';
+import Prompt from '../prompt.vue';
- export default {
- components: {
- prompt: Prompt,
+export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ outputType: {
+ type: String,
+ required: true,
},
- props: {
- outputType: {
- type: String,
- required: true,
- },
- rawCode: {
- type: String,
- required: true,
- },
+ rawCode: {
+ type: String,
+ required: true,
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue
index 4183b976814..d9f8604ed10 100644
--- a/app/assets/javascripts/notebook/cells/output/index.vue
+++ b/app/assets/javascripts/notebook/cells/output/index.vue
@@ -1,78 +1,78 @@
<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 {
- components: {
- 'code-cell': CodeCell,
- 'html-output': Html,
- 'image-output': Image,
+export default {
+ components: {
+ 'code-cell': CodeCell,
+ 'html-output': Html,
+ 'image-output': Image,
+ },
+ props: {
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- codeCssClass: {
- type: String,
- required: false,
- default: '',
- },
- count: {
- type: Number,
- required: false,
- default: 0,
- },
- output: {
- type: Object,
- requred: true,
- default: () => ({}),
- },
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
},
- 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';
- }
-
+ output: {
+ type: Object,
+ requred: true,
+ default: () => ({}),
+ },
+ },
+ computed: {
+ componentName() {
+ if (this.output.text) {
return 'code-cell';
- },
- rawCode() {
- if (this.output.text) {
- return this.output.text.join('');
- }
+ } 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 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 '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 'text/plain';
- },
+ 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>
diff --git a/app/assets/javascripts/notebook/cells/prompt.vue b/app/assets/javascripts/notebook/cells/prompt.vue
index fe1fc37e1dc..d96f701ee3e 100644
--- a/app/assets/javascripts/notebook/cells/prompt.vue
+++ b/app/assets/javascripts/notebook/cells/prompt.vue
@@ -1,23 +1,23 @@
<script>
- export default {
- props: {
- type: {
- type: String,
- required: false,
- default: '',
- },
- count: {
- type: Number,
- required: false,
- default: 0,
- },
+export default {
+ props: {
+ type: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- hasKeys() {
- return this.type !== '' && this.count;
- },
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
},
- };
+ },
+ computed: {
+ hasKeys() {
+ return this.type !== '' && this.count;
+ },
+ },
+};
</script>
<template>
@@ -29,9 +29,9 @@
</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 f241df9620d..c5cc8c97dda 100644
--- a/app/assets/javascripts/notebook/index.vue
+++ b/app/assets/javascripts/notebook/index.vue
@@ -1,51 +1,48 @@
<script>
- import {
- MarkdownCell,
- CodeCell,
- } from './cells';
+import { MarkdownCell, CodeCell } from './cells';
- export default {
- components: {
- 'code-cell': CodeCell,
- 'markdown-cell': MarkdownCell,
+export default {
+ components: {
+ 'code-cell': CodeCell,
+ 'markdown-cell': MarkdownCell,
+ },
+ props: {
+ notebook: {
+ type: Object,
+ required: true,
},
- props: {
- notebook: {
- type: Object,
- required: true,
- },
- codeCssClass: {
- type: String,
- required: false,
- default: '',
- },
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- cells() {
- if (this.notebook.worksheets) {
- const data = {
- cells: [],
- };
+ },
+ computed: {
+ cells() {
+ if (this.notebook.worksheets) {
+ const data = {
+ cells: [],
+ };
- return this.notebook.worksheets.reduce((cellData, sheet) => {
- const cellDataCopy = cellData;
- cellDataCopy.cells = cellDataCopy.cells.concat(sheet.cells);
- return cellDataCopy;
- }, data).cells;
- }
+ return this.notebook.worksheets.reduce((cellData, sheet) => {
+ const cellDataCopy = cellData;
+ cellDataCopy.cells = cellDataCopy.cells.concat(sheet.cells);
+ return cellDataCopy;
+ }, data).cells;
+ }
- return this.notebook.cells;
- },
- hasNotebook() {
- return Object.keys(this.notebook).length;
- },
+ return this.notebook.cells;
},
- methods: {
- cellType(type) {
- return `${type}-cell`;
- },
+ hasNotebook() {
+ return Object.keys(this.notebook).length;
},
- };
+ },
+ methods: {
+ cellType(type) {
+ return `${type}-cell`;
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 353aa790743..d9e99603238 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -94,6 +94,7 @@ export default {
class="diff-file file-holder"
>
<diff-file-header
+ :discussion-path="discussion.discussionPath"
:diff-file="diffFile"
:can-current-user-fork="false"
:discussions-expanded="isDiscussionsExpanded"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 33998394a69..38c43e5fe08 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -102,6 +102,18 @@ export default {
},
methods: {
...mapActions(['toggleResolveNote']),
+ shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState) {
+ // shouldBeResolved() checks the actual resolution state,
+ // considering batchComments (EEP), if applicable/enabled.
+ const newResolvedStateAfterUpdate =
+ this.shouldBeResolved && this.shouldBeResolved(shouldResolve);
+
+ const shouldToggleState =
+ newResolvedStateAfterUpdate !== undefined &&
+ beforeSubmitDiscussionState !== newResolvedStateAfterUpdate;
+
+ return shouldResolve || shouldToggleState;
+ },
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
@@ -109,7 +121,7 @@ export default {
this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => {
this.isSubmitting = false;
- if (shouldResolve) {
+ if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
this.resolveHandler(beforeSubmitDiscussionState);
}
});
diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
index 002b2279fcc..d0bce857029 100644
--- a/app/assets/javascripts/pages/groups/edit/index.js
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -2,6 +2,7 @@ import groupAvatar from '~/group_avatar';
import TransferDropdown from '~/groups/transfer_dropdown';
import initConfirmDangerModal from '~/confirm_danger_modal';
import initSettingsPanels from '~/settings_panels';
+import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import { GROUP_BADGE } from '~/badges/constants';
@@ -10,5 +11,8 @@ document.addEventListener('DOMContentLoaded', () => {
new TransferDropdown(); // eslint-disable-line no-new
initConfirmDangerModal();
initSettingsPanels();
+ dirtySubmitFactory(
+ document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'),
+ );
mountBadgeSettings(GROUP_BADGE);
});
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index b0345b4e50d..5659e13981a 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,5 +1,5 @@
+import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
-import PersistentUserCallout from '../../persistent_user_callout';
import Project from './project';
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
@@ -12,9 +12,7 @@ document.addEventListener('DOMContentLoaded', () => {
];
if (newClusterViews.indexOf(page) > -1) {
- const callout = document.querySelector('.gcp-signup-offer');
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
-
+ initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns();
}
diff --git a/app/assets/javascripts/pages/root/index.js b/app/assets/javascripts/pages/root/index.js
deleted file mode 100644
index 09f8185d3b5..00000000000
--- a/app/assets/javascripts/pages/root/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// if the "projects dashboard" is a user's default dashboard, when they visit the
-// instance root index, the dashboard will be served by the root controller instead
-// of a dashboard controller. The root index redirects for all other default dashboards.
-
-import '../dashboard/projects/index';
diff --git a/app/assets/javascripts/pages/users/user_overview_block.js b/app/assets/javascripts/pages/users/user_overview_block.js
index 0009419cd0c..2ed177be558 100644
--- a/app/assets/javascripts/pages/users/user_overview_block.js
+++ b/app/assets/javascripts/pages/users/user_overview_block.js
@@ -1,10 +1,15 @@
import axios from '~/lib/utils/axios_utils';
+const DEFAULT_LIMIT = 20;
+
export default class UserOverviewBlock {
constructor(options = {}) {
this.container = options.container;
this.url = options.url;
- this.limit = options.limit || 20;
+ this.requestParams = {
+ limit: DEFAULT_LIMIT,
+ ...options.requestParams,
+ };
this.loadData();
}
@@ -15,9 +20,7 @@ export default class UserOverviewBlock {
axios
.get(this.url, {
- params: {
- limit: this.limit,
- },
+ params: this.requestParams,
})
.then(({ data }) => this.render(data))
.catch(() => loadingEl.classList.add('hide'));
@@ -34,7 +37,9 @@ export default class UserOverviewBlock {
if (count && count > 0) {
document.querySelector(`${this.container} .js-view-all`).classList.remove('hide');
} else {
- document.querySelector(`${this.container} .nothing-here-block`).classList.add('text-left', 'p-0');
+ document
+ .querySelector(`${this.container} .nothing-here-block`)
+ .classList.add('text-left', 'p-0');
}
loadingEl.classList.add('hide');
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 23b0348a99f..1de9945baad 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -182,18 +182,22 @@ export default class UserTabs {
this.loadActivityCalendar('overview');
- UserTabs.renderMostRecentBlocks('#js-overview .activities-block', 5);
- UserTabs.renderMostRecentBlocks('#js-overview .projects-block', 10);
+ UserTabs.renderMostRecentBlocks('#js-overview .activities-block', {
+ requestParams: { limit: 5 },
+ });
+ UserTabs.renderMostRecentBlocks('#js-overview .projects-block', {
+ requestParams: { limit: 10, skip_pagination: true },
+ });
this.loaded.overview = true;
}
- static renderMostRecentBlocks(container, limit) {
+ static renderMostRecentBlocks(container, options) {
// eslint-disable-next-line no-new
new UserOverviewBlock({
container,
url: $(`${container} .overview-content-list`).data('href'),
- limit,
+ ...options,
});
}
@@ -216,7 +220,12 @@ export default class UserTabs {
let calendarHint = '';
if (action === 'activity') {
- calendarHint = sprintf(__('Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})'), { utcFormatted });
+ calendarHint = sprintf(
+ __(
+ 'Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})',
+ ),
+ { utcFormatted },
+ );
} else if (action === 'overview') {
calendarHint = __('Issues, merge requests, pushes and comments.');
}
@@ -224,7 +233,15 @@ export default class UserTabs {
$calendarWrap.find('.calendar-hint').text(calendarHint);
// eslint-disable-next-line no-new
- new ActivityCalendar('.tab-pane.active .js-contrib-calendar', '.tab-pane.active .user-calendar-activities', data, calendarActivitiesPath, utcOffset, 0, monthsAgo);
+ new ActivityCalendar(
+ '.tab-pane.active .js-contrib-calendar',
+ '.tab-pane.active .user-calendar-activities',
+ data,
+ calendarActivitiesPath,
+ utcOffset,
+ 0,
+ monthsAgo,
+ );
})
.catch(() => flash(__('There was an error loading users activity calendar.')));
}
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 1522e2227e4..300d453c174 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -1,5 +1,6 @@
<script>
import $ from 'jquery';
+import { glEmojiTag } from '~/emoji';
import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue';
@@ -64,6 +65,16 @@ export default {
lineProfileModal() {
return $('#modal-peek-line-profile');
},
+ hasHost() {
+ return this.currentRequest && this.currentRequest.details && this.currentRequest.details.host;
+ },
+ birdEmoji() {
+ if (this.hasHost && this.currentRequest.details.host.canary) {
+ return glEmojiTag('baby_chick');
+ }
+
+ return '';
+ },
},
mounted() {
this.currentRequest = this.requestId;
@@ -93,9 +104,11 @@ export default {
class="view"
>
<span
- v-if="currentRequest.details"
+ v-if="hasHost"
class="current-host"
+ :class="{ 'canary' : currentRequest.details.host.canary }"
>
+ <span v-html="birdEmoji"></span>
{{ currentRequest.details.host.hostname }}
</span>
</div>
diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js
deleted file mode 100644
index 1e34e74a152..00000000000
--- a/app/assets/javascripts/persistent_user_callout.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import axios from './lib/utils/axios_utils';
-import { __ } from './locale';
-import Flash from './flash';
-
-export default class PersistentUserCallout {
- constructor(container) {
- const { dismissEndpoint, featureId } = container.dataset;
- this.container = container;
- this.dismissEndpoint = dismissEndpoint;
- this.featureId = featureId;
-
- this.init();
- }
-
- init() {
- const closeButton = this.container.querySelector('.js-close');
- closeButton.addEventListener('click', event => this.dismiss(event));
- }
-
- dismiss(event) {
- event.preventDefault();
-
- axios
- .post(this.dismissEndpoint, {
- feature_name: this.featureId,
- })
- .then(() => {
- this.container.remove();
- })
- .catch(() => {
- Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
- });
- }
-}
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 64f3dde5be7..6b3753f7966 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -57,8 +57,6 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
$('.layout-page').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
-
- if (gl.lazyLoader) gl.lazyLoader.loadCheck();
}
$this.attr('data-original-title', tooltipLabel);
diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js
index 37b4a2a4c63..b38421bdf1c 100644
--- a/app/assets/javascripts/settings_panels.js
+++ b/app/assets/javascripts/settings_panels.js
@@ -1,7 +1,8 @@
import $ from 'jquery';
+import { __ } from './locale';
function expandSection($section) {
- $section.find('.js-settings-toggle').text('Collapse');
+ $section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Collapse'));
$section.find('.settings-content').off('scroll.expandSection').scrollTop(0);
$section.addClass('expanded');
if (!$section.hasClass('no-animate')) {
@@ -11,7 +12,7 @@ function expandSection($section) {
}
function closeSection($section) {
- $section.find('.js-settings-toggle').text('Expand');
+ $section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Expand'));
$section.find('.settings-content').on('scroll.expandSection', () => expandSection($section));
$section.removeClass('expanded');
if (!$section.hasClass('no-animate')) {
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 ab7fab7e5ca..cdff4105335 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -102,7 +102,6 @@ export default {
>
<icon
:name="lockIcon"
- aria-hidden="true"
class="sidebar-item-icon is-active"
/>
</div>
@@ -134,7 +133,6 @@ export default {
<icon
:size="16"
name="lock"
- aria-hidden="true"
class="sidebar-item-icon inline is-active"
/>
{{ __('Locked') }}
@@ -147,7 +145,6 @@ export default {
<icon
:size="16"
name="lock-open"
- aria-hidden="true"
class="sidebar-item-icon inline"
/>
{{ __('Unlocked') }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index 70518ad73e8..9161f703697 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -22,9 +22,7 @@ export default {
directives: {
tooltip,
},
- mixins: [
- timeagoMixin,
- ],
+ mixins: [timeagoMixin],
props: {
deployment: {
type: Object,
@@ -50,7 +48,7 @@ export default {
return !!(this.deployment.url && this.deployment.name);
},
hasMetrics() {
- return !!(this.deployment.metrics_url);
+ return !!this.deployment.metrics_url;
},
},
methods: {
@@ -63,7 +61,7 @@ export default {
MRWidgetService.stopEnvironment(this.deployment.stop_url)
.then(res => res.data)
- .then((data) => {
+ .then(data => {
if (data.redirect_url) {
visitUrl(data.redirect_url);
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
index 5e76f6b1cac..41dbc5c9cbb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
@@ -60,11 +60,29 @@ export default {
let memoryUsageMsg = '';
if (memoryTo > memoryFrom) {
- memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB'), messageProps, false);
+ memoryUsageMsg = sprintf(
+ s__(
+ 'mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB',
+ ),
+ messageProps,
+ false,
+ );
} else if (memoryTo < memoryFrom) {
- memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB'), messageProps, false);
+ memoryUsageMsg = sprintf(
+ s__(
+ 'mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB',
+ ),
+ messageProps,
+ false,
+ );
} else {
- memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB'), messageProps, false);
+ memoryUsageMsg = sprintf(
+ s__(
+ 'mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB',
+ ),
+ messageProps,
+ false,
+ );
}
return memoryUsageMsg;
@@ -77,7 +95,7 @@ export default {
methods: {
getMegabytes(bytesString) {
const valueInBytes = Number(bytesString).toFixed(2);
- return (bytesToMiB(valueInBytes)).toFixed(2);
+ return bytesToMiB(valueInBytes).toFixed(2);
},
computeGraphData(metrics, deploymentTime) {
this.loadingMetrics = false;
@@ -103,7 +121,7 @@ export default {
loadMetrics() {
backOff((next, stop) => {
MRWidgetService.fetchMetrics(this.metricsUrl)
- .then((res) => {
+ .then(res => {
if (res.status === statusCodes.NO_CONTENT) {
this.backOffRequestCounter += 1;
/* eslint-disable no-unused-expressions */
@@ -114,14 +132,14 @@ export default {
})
.catch(stop);
})
- .then((res) => {
+ .then(res => {
if (res.status === statusCodes.NO_CONTENT) {
return res;
}
return res.data;
})
- .then((data) => {
+ .then(data => {
this.computeGraphData(data.metrics, data.deployment_time);
return data;
})
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
index 22c2f74f900..2ad9e8be655 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
@@ -1,36 +1,36 @@
<script>
- import tooltip from '../../vue_shared/directives/tooltip';
+import tooltip from '../../vue_shared/directives/tooltip';
- export default {
- name: 'MrWidgetAuthor',
- directives: {
- tooltip,
+export default {
+ name: 'MrWidgetAuthor',
+ directives: {
+ tooltip,
+ },
+ props: {
+ author: {
+ type: Object,
+ required: true,
},
- props: {
- author: {
- type: Object,
- required: true,
- },
- showAuthorName: {
- type: Boolean,
- required: false,
- default: true,
- },
- showAuthorTooltip: {
- type: Boolean,
- required: false,
- default: false,
- },
+ showAuthorName: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- computed: {
- authorUrl() {
- return this.author.webUrl || this.author.web_url;
- },
- avatarUrl() {
- return this.author.avatarUrl || this.author.avatar_url;
- },
+ showAuthorTooltip: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- };
+ },
+ computed: {
+ authorUrl() {
+ return this.author.webUrl || this.author.web_url;
+ },
+ avatarUrl() {
+ return this.author.avatarUrl || this.author.avatar_url;
+ },
+ },
+};
</script>
<template>
<a
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue
index ba16cb9e2c8..1d902131f49 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue
@@ -1,34 +1,34 @@
<script>
- import tooltip from '~/vue_shared/directives/tooltip';
- import MrWidgetAuthor from './mr_widget_author.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import MrWidgetAuthor from './mr_widget_author.vue';
- export default {
- name: 'MrWidgetAuthorTime',
- components: {
- MrWidgetAuthor,
+export default {
+ name: 'MrWidgetAuthorTime',
+ components: {
+ MrWidgetAuthor,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ actionText: {
+ type: String,
+ required: true,
},
- directives: {
- tooltip,
+ author: {
+ type: Object,
+ required: true,
},
- props: {
- actionText: {
- type: String,
- required: true,
- },
- author: {
- type: Object,
- required: true,
- },
- dateTitle: {
- type: String,
- required: true,
- },
- dateReadable: {
- type: String,
- required: true,
- },
+ dateTitle: {
+ type: String,
+ required: true,
},
- };
+ dateReadable: {
+ type: String,
+ required: true,
+ },
+ },
+};
</script>
<template>
<h4 class="js-mr-widget-author">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
index 62b61e1f41f..37c6af13c03 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
@@ -1,24 +1,26 @@
<script>
- import { sprintf, s__ } from '~/locale';
+import { sprintf, s__ } from '~/locale';
- export default {
- name: 'MRWidgetMergeHelp',
- props: {
- missingBranch: {
- type: String,
- required: false,
- default: '',
- },
+export default {
+ name: 'MRWidgetMergeHelp',
+ props: {
+ missingBranch: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- missingBranchInfo() {
- return sprintf(
- s__('mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the'),
- { branch: this.missingBranch },
- );
- },
+ },
+ computed: {
+ missingBranchInfo() {
+ return sprintf(
+ s__(
+ 'mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the',
+ ),
+ { branch: this.missingBranch },
+ );
},
- };
+ },
+};
</script>
<template>
<section class="mr-widget-help">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
index 88d0fcd70f5..cc77b96a589 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
@@ -1,32 +1,32 @@
<script>
- import { s__ } from '~/locale';
+import { s__ } from '~/locale';
- export default {
- name: 'MRWidgetRelatedLinks',
- props: {
- relatedLinks: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- state: {
- type: String,
- required: false,
- default: '',
- },
+export default {
+ name: 'MRWidgetRelatedLinks',
+ props: {
+ relatedLinks: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- computed: {
- closesText() {
- if (this.state === 'merged') {
- return s__('mrWidget|Closed');
- }
- if (this.state === 'closed') {
- return s__('mrWidget|Did not close');
- }
- return s__('mrWidget|Closes');
- },
+ state: {
+ type: String,
+ required: false,
+ default: '',
},
- };
+ },
+ computed: {
+ closesText() {
+ if (this.state === 'merged') {
+ return s__('mrWidget|Closed');
+ }
+ if (this.state === 'closed') {
+ return s__('mrWidget|Did not close');
+ }
+ return s__('mrWidget|Closes');
+ },
+ },
+};
</script>
<template>
<section class="mr-info-list mr-links">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index 035ae791a1d..ba6a1687e51 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -1,33 +1,33 @@
<script>
- import ciIcon from '../../vue_shared/components/ci_icon.vue';
+import ciIcon from '../../vue_shared/components/ci_icon.vue';
- export default {
- components: {
- ciIcon,
+export default {
+ components: {
+ ciIcon,
+ },
+ props: {
+ status: {
+ type: String,
+ required: true,
},
- props: {
- status: {
- type: String,
- required: true,
- },
- showDisabledButton: {
- type: Boolean,
- required: false,
- default: false,
- },
+ showDisabledButton: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- computed: {
- isLoading() {
- return this.status === 'loading';
- },
- statusObj() {
- return {
- group: this.status,
- icon: `status_${this.status}`,
- };
- },
+ },
+ computed: {
+ isLoading() {
+ return this.status === 'loading';
},
- };
+ statusObj() {
+ return {
+ group: this.status,
+ icon: `status_${this.status}`,
+ };
+ },
+ },
+};
</script>
<template>
<div class="space-children d-flex append-right-10 widget-status-icon">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
index 56879c04d16..01f707163d4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
@@ -1,16 +1,16 @@
<script>
- import tooltip from '../../vue_shared/directives/tooltip';
- import { __ } from '../../locale';
+import tooltip from '../../vue_shared/directives/tooltip';
+import { __ } from '../../locale';
- export default {
- directives: {
- tooltip,
- },
- created() {
- this.removesBranchText = __('<strong>Removes</strong> source branch');
- this.tooltipTitle = __('A user with write access to the source branch selected this option');
- },
- };
+export default {
+ directives: {
+ tooltip,
+ },
+ created() {
+ this.removesBranchText = __('<strong>Removes</strong> source branch');
+ this.tooltipTitle = __('A user with write access to the source branch selected this option');
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
index cfbd44d41b2..2a76a878448 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
@@ -1,12 +1,12 @@
<script>
- import statusIcon from '../mr_widget_status_icon.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetArchived',
- components: {
- statusIcon,
- },
- };
+export default {
+ name: 'MRWidgetArchived',
+ components: {
+ statusIcon,
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
index 01294d5b40c..4f8b07484c0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -1,32 +1,32 @@
<script>
- import eventHub from '../../event_hub';
- import statusIcon from '../mr_widget_status_icon.vue';
+import eventHub from '../../event_hub';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetAutoMergeFailed',
- components: {
- statusIcon,
+export default {
+ name: 'MRWidgetAutoMergeFailed',
+ components: {
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
},
- props: {
- mr: {
- type: Object,
- required: true,
- },
+ },
+ data() {
+ return {
+ isRefreshing: false,
+ };
+ },
+ methods: {
+ refreshWidget() {
+ this.isRefreshing = true;
+ eventHub.$emit('MRWidgetUpdateRequested', () => {
+ this.isRefreshing = false;
+ });
},
- data() {
- return {
- isRefreshing: false,
- };
- },
- methods: {
- refreshWidget() {
- this.isRefreshing = true;
- eventHub.$emit('MRWidgetUpdateRequested', () => {
- this.isRefreshing = false;
- });
- },
- },
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
index ae6630dcd6f..fdf0a9fd994 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
@@ -1,12 +1,12 @@
<script>
- import statusIcon from '../mr_widget_status_icon.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetChecking',
- components: {
- statusIcon,
- },
- };
+export default {
+ name: 'MRWidgetChecking',
+ components: {
+ statusIcon,
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
index 25a57e520ee..f06eab95c7e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
@@ -1,23 +1,23 @@
<script>
- import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
- import statusIcon from '../mr_widget_status_icon.vue';
+import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetClosed',
- components: {
- MrWidgetAuthorTime,
- statusIcon,
- },
- props: {
- /* TODO: This is providing all store and service down when it
+export default {
+ name: 'MRWidgetClosed',
+ components: {
+ MrWidgetAuthorTime,
+ statusIcon,
+ },
+ props: {
+ /* TODO: This is providing all store and service down when it
only needs metrics and targetBranch */
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
index dff9ec657b9..8c808296178 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -1,21 +1,21 @@
<script>
- import statusIcon from '../mr_widget_status_icon.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetConflicts',
- components: {
- statusIcon,
- },
- props: {
- /* TODO: This is providing all store and service down when it
+export default {
+ name: 'MRWidgetConflicts',
+ components: {
+ statusIcon,
+ },
+ props: {
+ /* TODO: This is providing all store and service down when it
only needs a few props */
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
index 97f4196b94d..484b5600d63 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
@@ -1,82 +1,82 @@
<script>
- import Flash from '../../../flash';
- import statusIcon from '../mr_widget_status_icon.vue';
- import MrWidgetAuthor from '../../components/mr_widget_author.vue';
- import eventHub from '../../event_hub';
+import Flash from '../../../flash';
+import statusIcon from '../mr_widget_status_icon.vue';
+import MrWidgetAuthor from '../../components/mr_widget_author.vue';
+import eventHub from '../../event_hub';
- export default {
- name: 'MRWidgetMergeWhenPipelineSucceeds',
- components: {
- MrWidgetAuthor,
- statusIcon,
+export default {
+ name: 'MRWidgetMergeWhenPipelineSucceeds',
+ components: {
+ MrWidgetAuthor,
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- props: {
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- service: {
- type: Object,
- required: true,
- default: () => ({}),
- },
+ service: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- data() {
- return {
- isCancellingAutoMerge: false,
- isRemovingSourceBranch: false,
- };
- },
- computed: {
- canRemoveSourceBranch() {
- const {
- shouldRemoveSourceBranch,
- canRemoveSourceBranch,
- mergeUserId,
- currentUserId,
- } = this.mr;
+ },
+ data() {
+ return {
+ isCancellingAutoMerge: false,
+ isRemovingSourceBranch: false,
+ };
+ },
+ computed: {
+ canRemoveSourceBranch() {
+ const {
+ shouldRemoveSourceBranch,
+ canRemoveSourceBranch,
+ mergeUserId,
+ currentUserId,
+ } = this.mr;
- return !shouldRemoveSourceBranch &&
- canRemoveSourceBranch &&
- mergeUserId === currentUserId;
- },
+ return !shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId;
},
- methods: {
- cancelAutomaticMerge() {
- this.isCancellingAutoMerge = true;
- this.service.cancelAutomaticMerge()
- .then(res => res.data)
- .then((data) => {
- eventHub.$emit('UpdateWidgetData', data);
- })
- .catch(() => {
- this.isCancellingAutoMerge = false;
- Flash('Something went wrong. Please try again.');
- });
- },
- removeSourceBranch() {
- const options = {
- sha: this.mr.sha,
- merge_when_pipeline_succeeds: true,
- should_remove_source_branch: true,
- };
+ },
+ methods: {
+ cancelAutomaticMerge() {
+ this.isCancellingAutoMerge = true;
+ this.service
+ .cancelAutomaticMerge()
+ .then(res => res.data)
+ .then(data => {
+ eventHub.$emit('UpdateWidgetData', data);
+ })
+ .catch(() => {
+ this.isCancellingAutoMerge = false;
+ Flash('Something went wrong. Please try again.');
+ });
+ },
+ removeSourceBranch() {
+ const options = {
+ sha: this.mr.sha,
+ merge_when_pipeline_succeeds: true,
+ should_remove_source_branch: true,
+ };
- this.isRemovingSourceBranch = true;
- this.service.merge(options)
- .then(res => res.data)
- .then((data) => {
- if (data.status === 'merge_when_pipeline_succeeds') {
- eventHub.$emit('MRWidgetUpdateRequested');
- }
- })
- .catch(() => {
- this.isRemovingSourceBranch = false;
- Flash('Something went wrong. Please try again.');
- });
- },
+ this.isRemovingSourceBranch = true;
+ this.service
+ .merge(options)
+ .then(res => res.data)
+ .then(data => {
+ if (data.status === 'merge_when_pipeline_succeeds') {
+ eventHub.$emit('MRWidgetUpdateRequested');
+ }
+ })
+ .catch(() => {
+ this.isRemovingSourceBranch = false;
+ Flash('Something went wrong. Please try again.');
+ });
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
index 8184ef33022..656c3b5c47e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
@@ -1,106 +1,100 @@
<script>
- import Flash from '~/flash';
- import tooltip from '~/vue_shared/directives/tooltip';
- import { s__, __ } from '~/locale';
- import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
- import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
- import statusIcon from '../mr_widget_status_icon.vue';
- import eventHub from '../../event_hub';
+import Flash from '~/flash';
+import tooltip from '~/vue_shared/directives/tooltip';
+import { s__, __ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
+import eventHub from '../../event_hub';
- export default {
- name: 'MRWidgetMerged',
- directives: {
- tooltip,
+export default {
+ name: 'MRWidgetMerged',
+ directives: {
+ tooltip,
+ },
+ components: {
+ MrWidgetAuthorTime,
+ statusIcon,
+ ClipboardButton,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- components: {
- MrWidgetAuthorTime,
- statusIcon,
- ClipboardButton,
+ service: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- props: {
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- service: {
- type: Object,
- required: true,
- default: () => ({}),
- },
+ },
+ data() {
+ return {
+ isMakingRequest: false,
+ };
+ },
+ computed: {
+ shouldShowRemoveSourceBranch() {
+ const { sourceBranchRemoved, isRemovingSourceBranch, canRemoveSourceBranch } = this.mr;
+
+ return (
+ !sourceBranchRemoved &&
+ canRemoveSourceBranch &&
+ !this.isMakingRequest &&
+ !isRemovingSourceBranch
+ );
},
- data() {
- return {
- isMakingRequest: false,
- };
+ shouldShowSourceBranchRemoving() {
+ const { sourceBranchRemoved, isRemovingSourceBranch } = this.mr;
+ return !sourceBranchRemoved && (isRemovingSourceBranch || this.isMakingRequest);
},
- computed: {
- shouldShowRemoveSourceBranch() {
- const {
- sourceBranchRemoved,
- isRemovingSourceBranch,
- canRemoveSourceBranch,
- } = this.mr;
-
- return !sourceBranchRemoved &&
- canRemoveSourceBranch &&
- !this.isMakingRequest &&
- !isRemovingSourceBranch;
- },
- shouldShowSourceBranchRemoving() {
- const {
- sourceBranchRemoved,
- isRemovingSourceBranch,
- } = this.mr;
- return !sourceBranchRemoved &&
- (isRemovingSourceBranch || this.isMakingRequest);
- },
- shouldShowMergedButtons() {
- const {
- canRevertInCurrentMR,
- canCherryPickInCurrentMR,
- revertInForkPath,
- cherryPickInForkPath,
- } = this.mr;
+ shouldShowMergedButtons() {
+ const {
+ canRevertInCurrentMR,
+ canCherryPickInCurrentMR,
+ revertInForkPath,
+ cherryPickInForkPath,
+ } = this.mr;
- return canRevertInCurrentMR ||
- canCherryPickInCurrentMR ||
- revertInForkPath ||
- cherryPickInForkPath;
- },
- revertTitle() {
- return s__('mrWidget|Revert this merge request in a new merge request');
- },
- cherryPickTitle() {
- return s__('mrWidget|Cherry-pick this merge request in a new merge request');
- },
- revertLabel() {
- return s__('mrWidget|Revert');
- },
- cherryPickLabel() {
- return s__('mrWidget|Cherry-pick');
- },
+ return (
+ canRevertInCurrentMR || canCherryPickInCurrentMR || revertInForkPath || cherryPickInForkPath
+ );
+ },
+ revertTitle() {
+ return s__('mrWidget|Revert this merge request in a new merge request');
+ },
+ cherryPickTitle() {
+ return s__('mrWidget|Cherry-pick this merge request in a new merge request');
},
- methods: {
- removeSourceBranch() {
- this.isMakingRequest = true;
+ revertLabel() {
+ return s__('mrWidget|Revert');
+ },
+ cherryPickLabel() {
+ return s__('mrWidget|Cherry-pick');
+ },
+ },
+ methods: {
+ removeSourceBranch() {
+ this.isMakingRequest = true;
- this.service.removeSourceBranch()
- .then(res => res.data)
- .then((data) => {
- if (data.message === 'Branch was removed') {
- eventHub.$emit('MRWidgetUpdateRequested', () => {
- this.isMakingRequest = false;
- });
- }
- })
- .catch(() => {
- this.isMakingRequest = false;
- Flash(__('Something went wrong. Please try again.'));
- });
- },
+ this.service
+ .removeSourceBranch()
+ .then(res => res.data)
+ .then(data => {
+ if (data.message === 'Branch was removed') {
+ eventHub.$emit('MRWidgetUpdateRequested', () => {
+ this.isMakingRequest = false;
+ });
+ }
+ })
+ .catch(() => {
+ this.isMakingRequest = false;
+ Flash(__('Something went wrong. Please try again.'));
+ });
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
@@ -162,18 +156,20 @@
<span class="label-branch">
<a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a>
</span>
- with
- <a
- :href="mr.mergeCommitPath"
- class="commit-sha js-mr-merged-commit-sha"
- v-text="mr.shortMergeCommitSha"
- >
- </a>
- <clipboard-button
- :title="__('Copy commit SHA to clipboard')"
- :text="mr.mergeCommitSha"
- css-class="btn-default btn-transparent btn-clipboard js-mr-merged-copy-sha"
- />
+ <template v-if="mr.mergeCommitSha">
+ with
+ <a
+ :href="mr.mergeCommitPath"
+ class="commit-sha js-mr-merged-commit-sha"
+ v-text="mr.shortMergeCommitSha"
+ >
+ </a>
+ <clipboard-button
+ :title="__('Copy commit SHA to clipboard')"
+ :text="mr.mergeCommitSha"
+ css-class="btn-default btn-transparent btn-clipboard js-mr-merged-copy-sha"
+ />
+ </template>
</p>
<p v-if="mr.sourceBranchRemoved">
{{ s__("mrWidget|The source branch has been removed") }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
index 953ddf40a51..139e64d1878 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
@@ -1,19 +1,19 @@
<script>
- import statusIcon from '../mr_widget_status_icon.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetMerging',
- components: {
- statusIcon,
+export default {
+ name: 'MRWidgetMerging',
+ components: {
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- props: {
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- },
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body mr-state-locked media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
index b0e96f74626..227e9b85f9d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
@@ -1,40 +1,48 @@
<script>
- import { sprintf, s__ } from '~/locale';
- import tooltip from '~/vue_shared/directives/tooltip';
- import statusIcon from '../mr_widget_status_icon.vue';
- import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
+import { sprintf, s__ } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+import statusIcon from '../mr_widget_status_icon.vue';
+import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
- export default {
- name: 'MRWidgetMissingBranch',
- directives: {
- tooltip,
+export default {
+ name: 'MRWidgetMissingBranch',
+ directives: {
+ tooltip,
+ },
+ components: {
+ mrWidgetMergeHelp,
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
},
- components: {
- mrWidgetMergeHelp,
- statusIcon,
+ },
+ computed: {
+ missingBranchName() {
+ return this.mr.sourceBranchRemoved ? 'source' : 'target';
},
- props: {
- mr: {
- type: Object,
- required: true,
- },
- },
- computed: {
- missingBranchName() {
- return this.mr.sourceBranchRemoved ? 'source' : 'target';
- },
- missingBranchNameMessage() {
- return sprintf(s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'), {
+ missingBranchNameMessage() {
+ return sprintf(
+ s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'),
+ {
missingBranchName: this.missingBranchName,
- });
- },
- message() {
- return sprintf(s__('mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line'), {
+ },
+ );
+ },
+ message() {
+ return sprintf(
+ s__(
+ 'mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line',
+ ),
+ {
missingBranchName: this.missingBranchName,
- });
- },
+ },
+ );
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue
index 92eee2cf5dd..360559ae0f0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue
@@ -1,12 +1,12 @@
<script>
- import StatusIcon from '../mr_widget_status_icon.vue';
+import StatusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetNotAllowed',
- components: {
- StatusIcon,
- },
- };
+export default {
+ name: 'MRWidgetNotAllowed',
+ components: {
+ StatusIcon,
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
index 37ee5215cea..a4eb5afb21c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
@@ -1,12 +1,12 @@
<script>
- import StatusIcon from '../mr_widget_status_icon.vue';
+import StatusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetPipelineBlocked',
- components: {
- StatusIcon,
- },
- };
+export default {
+ name: 'MRWidgetPipelineBlocked',
+ components: {
+ StatusIcon,
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
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 f31c7a3edb8..041fa13a8f5 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
@@ -1,85 +1,87 @@
<script>
- import simplePoll from '../../../lib/utils/simple_poll';
- import eventHub from '../../event_hub';
- import statusIcon from '../mr_widget_status_icon.vue';
- import Flash from '../../../flash';
+import simplePoll from '../../../lib/utils/simple_poll';
+import eventHub from '../../event_hub';
+import statusIcon from '../mr_widget_status_icon.vue';
+import Flash from '../../../flash';
- export default {
- name: 'MRWidgetRebase',
- components: {
- statusIcon,
+export default {
+ name: 'MRWidgetRebase',
+ components: {
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
},
- props: {
- mr: {
- type: Object,
- required: true,
- },
- service: {
- type: Object,
- required: true,
- },
+ service: {
+ type: Object,
+ required: true,
},
- data() {
- return {
- isMakingRequest: false,
- rebasingError: null,
- };
+ },
+ data() {
+ return {
+ isMakingRequest: false,
+ rebasingError: null,
+ };
+ },
+ computed: {
+ status() {
+ if (this.mr.rebaseInProgress || this.isMakingRequest) {
+ return 'loading';
+ }
+ if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) {
+ return 'warning';
+ }
+ return 'success';
},
- computed: {
- status() {
- if (this.mr.rebaseInProgress || this.isMakingRequest) {
- return 'loading';
- }
- if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) {
- return 'warning';
- }
- return 'success';
- },
- showDisabledButton() {
- return ['failed', 'loading'].includes(this.status);
- },
+ showDisabledButton() {
+ return ['failed', 'loading'].includes(this.status);
},
- methods: {
- rebase() {
- this.isMakingRequest = true;
- this.rebasingError = null;
+ },
+ methods: {
+ rebase() {
+ this.isMakingRequest = true;
+ this.rebasingError = null;
- this.service.rebase()
- .then(() => {
- simplePoll(this.checkRebaseStatus);
- })
- .catch((error) => {
- this.rebasingError = error.merge_error;
+ this.service
+ .rebase()
+ .then(() => {
+ simplePoll(this.checkRebaseStatus);
+ })
+ .catch(error => {
+ this.rebasingError = error.merge_error;
+ this.isMakingRequest = false;
+ Flash('Something went wrong. Please try again.');
+ });
+ },
+ checkRebaseStatus(continuePolling, stopPolling) {
+ this.service
+ .poll()
+ .then(res => res.data)
+ .then(res => {
+ if (res.rebase_in_progress) {
+ continuePolling();
+ } else {
this.isMakingRequest = false;
- Flash('Something went wrong. Please try again.');
- });
- },
- checkRebaseStatus(continuePolling, stopPolling) {
- this.service.poll()
- .then(res => res.data)
- .then((res) => {
- if (res.rebase_in_progress) {
- continuePolling();
- } else {
- this.isMakingRequest = false;
-
- if (res.merge_error && res.merge_error.length) {
- this.rebasingError = res.merge_error;
- Flash('Something went wrong. Please try again.');
- }
- eventHub.$emit('MRWidgetUpdateRequested');
- stopPolling();
+ if (res.merge_error && res.merge_error.length) {
+ this.rebasingError = res.merge_error;
+ Flash('Something went wrong. Please try again.');
}
- })
- .catch(() => {
- this.isMakingRequest = false;
- Flash('Something went wrong. Please try again.');
+
+ eventHub.$emit('MRWidgetUpdateRequested');
stopPolling();
- });
- },
+ }
+ })
+ .catch(() => {
+ this.isMakingRequest = false;
+ Flash('Something went wrong. Please try again.');
+ stopPolling();
+ });
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
index 89c9a41f316..9129fcbb918 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
@@ -24,9 +24,10 @@ export default {
methods: {
removeWIP() {
this.isMakingRequest = true;
- this.service.removeWIP()
+ this.service
+ .removeWIP()
.then(res => res.data)
- .then((data) => {
+ .then(data => {
eventHub.$emit('UpdateWidgetData', data);
new window.Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line
$('.merge-request .detail-page-description .title').text(this.mr.title);
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 0e445a29de4..7ac3fedb2e3 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -1,5 +1,4 @@
<script>
-
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import createFlash from '../flash';
@@ -100,8 +99,11 @@ export default {
return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
},
shouldRenderSourceBranchRemovalStatus() {
- return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch &&
- (!this.mr.isNothingToMergeState && !this.mr.isMergedState);
+ return (
+ !this.mr.canRemoveSourceBranch &&
+ this.mr.shouldRemoveSourceBranch &&
+ (!this.mr.isNothingToMergeState && !this.mr.isMergedState)
+ );
},
},
created() {
@@ -133,9 +135,10 @@ export default {
return new MRWidgetService(endpoints);
},
checkStatus(cb) {
- return this.service.checkStatus()
+ return this.service
+ .checkStatus()
.then(res => res.data)
- .then((data) => {
+ .then(data => {
this.handleNotification(data);
this.mr.setData(data);
this.setFaviconHelper();
@@ -172,20 +175,24 @@ export default {
return Promise.resolve();
},
fetchDeployments() {
- return this.service.fetchDeployments()
+ return this.service
+ .fetchDeployments()
.then(res => res.data)
- .then((data) => {
+ .then(data => {
if (data.length) {
this.mr.deployments = data;
}
})
.catch(() => {
- createFlash('Something went wrong while fetching the environments for this merge request. Please try again.');
+ createFlash(
+ 'Something went wrong while fetching the environments for this merge request. Please try again.',
+ );
});
},
fetchActionsContent() {
- this.service.fetchMergeActionsContent()
- .then((res) => {
+ this.service
+ .fetchMergeActionsContent()
+ .then(res => {
if (res.data) {
const el = document.createElement('div');
el.innerHTML = res.data;
@@ -212,22 +219,22 @@ export default {
this.pollingInterval.stopTimer();
},
bindEventHubListeners() {
- eventHub.$on('MRWidgetUpdateRequested', (cb) => {
+ eventHub.$on('MRWidgetUpdateRequested', cb => {
this.checkStatus(cb);
});
// `params` should be an Array contains a Boolean, like `[true]`
// Passing parameter as Boolean didn't work.
- eventHub.$on('SetBranchRemoveFlag', (params) => {
+ eventHub.$on('SetBranchRemoveFlag', params => {
[this.mr.isRemovingSourceBranch] = params;
});
- eventHub.$on('FailedToMerge', (mergeError) => {
+ eventHub.$on('FailedToMerge', mergeError => {
this.mr.state = 'failedToMerge';
this.mr.mergeError = mergeError;
});
- eventHub.$on('UpdateWidgetData', (data) => {
+ eventHub.$on('UpdateWidgetData', data => {
this.mr.setData(data);
});
@@ -268,11 +275,12 @@ export default {
:key="deployment.id"
:deployment="deployment"
/>
+ <grouped-test-reports-app
+ v-if="mr.testResultsPath"
+ class="js-reports-container"
+ :endpoint="mr.testResultsPath"
+ />
<div class="mr-section-container">
- <grouped-test-reports-app
- v-if="mr.testResultsPath"
- :endpoint="mr.testResultsPath"
- />
<div class="mr-widget-section">
<component
:is="componentName"
diff --git a/app/assets/javascripts/vue_shared/components/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue
index 33af7a7f1df..690dd794ba4 100644
--- a/app/assets/javascripts/vue_shared/components/bar_chart.vue
+++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue
@@ -118,7 +118,9 @@ export default {
this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0;
const yCoord = this.vbHeight / 2 + rectWidth - 5;
- return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${this.yAxisTextRotation})`;
+ return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${
+ this.yAxisTextRotation
+ })`;
},
},
mounted() {
@@ -207,8 +209,7 @@ export default {
renderedYAxis.selectAll('.tick').each(function createTickLines(d, i) {
if (i > 0) {
- d3
- .select(this)
+ d3.select(this)
.select('line')
.attr('x2', width)
.attr('class', 'axis-tick');
@@ -217,8 +218,7 @@ export default {
// Add the panning capabilities
if (this.isPanAvailable) {
- d3
- .select(this.$refs.baseSvg)
+ d3.select(this.$refs.baseSvg)
.call(this.zoom)
.on('wheel.zoom', null); // This disables the pan of the graph with the scroll of the mouse wheel
}
diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
index 9c1e5c68649..4d63296e332 100644
--- a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
@@ -1,85 +1,85 @@
<script>
- /* eslint-disable vue/require-default-prop */
- export default {
- name: 'DeprecatedModal', // use GlModal instead
+/* eslint-disable vue/require-default-prop */
+export default {
+ name: 'DeprecatedModal', // use GlModal instead
- 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: '',
- },
- secondaryButtonLabel: {
- type: String,
- required: false,
- default: '',
- },
- submitDisabled: {
- type: Boolean,
- required: false,
- default: 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: '',
+ },
+ secondaryButtonLabel: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ submitDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
- computed: {
- btnKindClass() {
- return {
- [`btn-${this.kind}`]: true,
- };
- },
- btnCancelKindClass() {
- return {
- [`btn-${this.closeKind}`]: true,
- };
- },
+ computed: {
+ btnKindClass() {
+ return {
+ [`btn-${this.kind}`]: 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>
diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
index 9bca1993ccc..19611b14be4 100644
--- a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
+++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
@@ -583,7 +583,5 @@ const fileNameIcons = {
};
export default function getIconForFile(name) {
- return fileNameIcons[name] ||
- fileExtensionIcons[name ? name.split('.').pop() : ''] ||
- '';
+ return fileNameIcons[name] || fileExtensionIcons[name ? name.split('.').pop() : ''] || '';
}
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
index b023c5cfeb1..b5444d43ded 100644
--- a/app/assets/javascripts/vue_shared/components/gl_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -41,10 +41,14 @@ export default {
},
},
mounted() {
- $(this.$el).on('shown.bs.modal', this.opened).on('hidden.bs.modal', this.closed);
+ $(this.$el)
+ .on('shown.bs.modal', this.opened)
+ .on('hidden.bs.modal', this.closed);
},
beforeDestroy() {
- $(this.$el).off('shown.bs.modal', this.opened).off('hidden.bs.modal', this.closed);
+ $(this.$el)
+ .off('shown.bs.modal', this.opened)
+ .off('hidden.bs.modal', this.closed);
},
methods: {
emitCancel(event) {
@@ -103,7 +107,7 @@ export default {
<slot name="footer">
<button
type="button"
- class="btn js-modal-cancel-action"
+ class="btn js-modal-cancel-action qa-modal-cancel-button"
data-dismiss="modal"
@click="emitCancel($event)"
>
@@ -112,7 +116,7 @@ export default {
<button
:class="`btn-${footerPrimaryButtonVariant}`"
type="button"
- class="btn js-modal-primary-action"
+ class="btn js-modal-primary-action qa-modal-primary-button"
data-dismiss="modal"
@click="emitSubmit($event)"
>
diff --git a/app/assets/javascripts/vue_shared/components/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue
index 5e0e7315e99..26f9d5ddc91 100644
--- a/app/assets/javascripts/vue_shared/components/icon.vue
+++ b/app/assets/javascripts/vue_shared/components/icon.vue
@@ -1,5 +1,4 @@
<script>
-
// only allow classes in images.scss e.g. s12
const validSizes = [8, 10, 12, 16, 18, 24, 32, 48, 72];
let iconValidator = () => true;
@@ -105,6 +104,7 @@ export default {
:x="x"
:y="y"
:tabindex="tabIndex"
+ aria-hidden="true"
>
<use v-bind="{ 'xlink:href':spriteHref }"/>
</svg>
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 ca8ce554588..dc88749c18f 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
@@ -1,34 +1,34 @@
<script>
- import icon from '../../../vue_shared/components/icon.vue';
+import icon from '../../../vue_shared/components/icon.vue';
- export default {
- components: {
- icon,
+export default {
+ components: {
+ icon,
+ },
+ props: {
+ isLocked: {
+ type: Boolean,
+ default: false,
+ required: false,
},
- props: {
- isLocked: {
- type: Boolean,
- default: false,
- required: false,
- },
- isConfidential: {
- type: Boolean,
- default: false,
- required: false,
- },
+ isConfidential: {
+ type: Boolean,
+ default: false,
+ required: false,
},
- computed: {
- warningIcon() {
- if (this.isConfidential) return 'eye-slash';
- if (this.isLocked) return 'lock';
+ },
+ computed: {
+ warningIcon() {
+ if (this.isConfidential) return 'eye-slash';
+ if (this.isLocked) return 'lock';
- return '';
- },
- isLockedAndConfidential() {
- return this.isConfidential && this.isLocked;
- },
+ return '';
},
- };
+ isLockedAndConfidential() {
+ return this.isConfidential && this.isLocked;
+ },
+ },
+};
</script>
<template>
<div class="issuable-note-warning">
@@ -37,7 +37,6 @@
:name="warningIcon"
:size="16"
class="icon inline"
- aria-hidden="true"
/>
<span v-if="isLockedAndConfidential">
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
index 4cbd3e6429d..f9b7fd5b1f9 100644
--- a/app/assets/javascripts/vue_shared/components/loading_button.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -1,6 +1,6 @@
<script>
- /* eslint-disable vue/require-default-prop */
- /* This is a re-usable vue component for rendering a button
+/* 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
@@ -17,34 +17,34 @@
*/
- export default {
- props: {
- loading: {
- type: Boolean,
- required: false,
- default: false,
- },
- disabled: {
- type: Boolean,
- required: false,
- default: false,
- },
- label: {
- type: String,
- required: false,
- },
- containerClass: {
- type: [String, Array, Object],
- required: false,
- default: 'btn btn-align-content',
- },
+export default {
+ props: {
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- methods: {
- onClick(e) {
- this.$emit('click', e);
- },
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- };
+ label: {
+ type: String,
+ required: false,
+ },
+ containerClass: {
+ type: [String, Array, Object],
+ required: false,
+ default: 'btn btn-align-content',
+ },
+ },
+ methods: {
+ onClick(e) {
+ this.$emit('click', e);
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 10e8ddad9cd..4687de62614 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -1,141 +1,141 @@
<script>
- import $ from 'jquery';
- import { s__ } from '~/locale';
- import Flash from '../../../flash';
- import GLForm from '../../../gl_form';
- import markdownHeader from './header.vue';
- import markdownToolbar from './toolbar.vue';
- import icon from '../icon.vue';
+import $ from 'jquery';
+import { s__ } from '~/locale';
+import Flash from '../../../flash';
+import GLForm from '../../../gl_form';
+import markdownHeader from './header.vue';
+import markdownToolbar from './toolbar.vue';
+import icon from '../icon.vue';
- export default {
- components: {
- markdownHeader,
- markdownToolbar,
- icon,
+export default {
+ components: {
+ markdownHeader,
+ markdownToolbar,
+ icon,
+ },
+ props: {
+ markdownPreviewPath: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- markdownPreviewPath: {
- type: String,
- required: false,
- default: '',
- },
- markdownDocsPath: {
- type: String,
- required: true,
- },
- markdownVersion: {
- type: Number,
- required: false,
- default: 0,
- },
- addSpacingClasses: {
- type: Boolean,
- required: false,
- default: true,
- },
- quickActionsDocsPath: {
- type: String,
- required: false,
- default: '',
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
- enableAutocomplete: {
- type: Boolean,
- required: false,
- default: true,
- },
+ markdownDocsPath: {
+ type: String,
+ required: true,
},
- data() {
- return {
- markdownPreview: '',
- referencedCommands: '',
- referencedUsers: '',
- markdownPreviewLoading: false,
- previewMarkdown: false,
- };
+ markdownVersion: {
+ type: Number,
+ required: false,
+ default: 0,
},
- computed: {
- shouldShowReferencedUsers() {
- const referencedUsersThreshold = 10;
- return this.referencedUsers.length >= referencedUsersThreshold;
- },
+ addSpacingClasses: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- mounted() {
- /*
- GLForm class handles all the toolbar buttons
- */
- return new GLForm($(this.$refs['gl-form']), {
- emojis: this.enableAutocomplete,
- members: this.enableAutocomplete,
- issues: this.enableAutocomplete,
- mergeRequests: this.enableAutocomplete,
- epics: this.enableAutocomplete,
- milestones: this.enableAutocomplete,
- labels: this.enableAutocomplete,
- snippets: this.enableAutocomplete,
- });
+ quickActionsDocsPath: {
+ type: String,
+ required: false,
+ default: '',
},
- beforeDestroy() {
- const glForm = $(this.$refs['gl-form']).data('glForm');
- if (glForm) {
- glForm.destroy();
- }
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ enableAutocomplete: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- methods: {
- showPreviewTab() {
- if (this.previewMarkdown) return;
+ },
+ data() {
+ return {
+ markdownPreview: '',
+ referencedCommands: '',
+ referencedUsers: '',
+ markdownPreviewLoading: false,
+ previewMarkdown: false,
+ };
+ },
+ 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']), {
+ emojis: this.enableAutocomplete,
+ members: this.enableAutocomplete,
+ issues: this.enableAutocomplete,
+ mergeRequests: this.enableAutocomplete,
+ epics: this.enableAutocomplete,
+ milestones: this.enableAutocomplete,
+ labels: this.enableAutocomplete,
+ snippets: this.enableAutocomplete,
+ });
+ },
+ beforeDestroy() {
+ const glForm = $(this.$refs['gl-form']).data('glForm');
+ if (glForm) {
+ glForm.destroy();
+ }
+ },
+ methods: {
+ showPreviewTab() {
+ if (this.previewMarkdown) return;
- this.previewMarkdown = true;
+ this.previewMarkdown = true;
- /*
+ /*
Can't use `$refs` as the component is technically in the parent component
so we access the VNode & then get the element
*/
- const text = this.$slots.textarea[0].elm.value;
+ const text = this.$slots.textarea[0].elm.value;
- if (text) {
- this.markdownPreviewLoading = true;
- this.$http
- .post(this.versionedPreviewPath(), { text })
- .then(resp => resp.json())
- .then(data => this.renderMarkdown(data))
- .catch(() => new Flash(s__('Error loading markdown preview')));
- } else {
- this.renderMarkdown();
- }
- },
+ if (text) {
+ this.markdownPreviewLoading = true;
+ this.$http
+ .post(this.versionedPreviewPath(), { text })
+ .then(resp => resp.json())
+ .then(data => this.renderMarkdown(data))
+ .catch(() => new Flash(s__('Error loading markdown preview')));
+ } else {
+ this.renderMarkdown();
+ }
+ },
- showWriteTab() {
- this.markdownPreview = '';
- this.previewMarkdown = false;
- },
+ showWriteTab() {
+ this.markdownPreview = '';
+ this.previewMarkdown = false;
+ },
- renderMarkdown(data = {}) {
- this.markdownPreviewLoading = false;
- this.markdownPreview = data.body || 'Nothing to preview.';
+ renderMarkdown(data = {}) {
+ this.markdownPreviewLoading = false;
+ this.markdownPreview = data.body || 'Nothing to preview.';
- if (data.references) {
- this.referencedCommands = data.references.commands;
- this.referencedUsers = data.references.users;
- }
+ if (data.references) {
+ this.referencedCommands = data.references.commands;
+ this.referencedUsers = data.references.users;
+ }
- this.$nextTick(() => {
- $(this.$refs['markdown-preview']).renderGFM();
- });
- },
+ this.$nextTick(() => {
+ $(this.$refs['markdown-preview']).renderGFM();
+ });
+ },
- versionedPreviewPath() {
- const { markdownPreviewPath, markdownVersion } = this;
- return `${markdownPreviewPath}${
- markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
- }markdown_version=${markdownVersion}`;
- },
+ versionedPreviewPath() {
+ const { markdownPreviewPath, markdownVersion } = this;
+ return `${markdownPreviewPath}${
+ markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
+ }markdown_version=${markdownVersion}`;
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index ccd53158820..704adf7864f 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,63 +1,64 @@
<script>
- import $ from 'jquery';
- import tooltip from '../../directives/tooltip';
- import toolbarButton from './toolbar_button.vue';
- import icon from '../icon.vue';
+import $ from 'jquery';
+import tooltip from '../../directives/tooltip';
+import toolbarButton from './toolbar_button.vue';
+import icon from '../icon.vue';
- export default {
- directives: {
- tooltip,
+export default {
+ directives: {
+ tooltip,
+ },
+ components: {
+ toolbarButton,
+ icon,
+ },
+ props: {
+ previewMarkdown: {
+ type: Boolean,
+ required: true,
},
- components: {
- toolbarButton,
- icon,
+ },
+ computed: {
+ mdTable() {
+ return [
+ '| header | header |',
+ '| ------ | ------ |',
+ '| cell | cell |',
+ '| cell | cell |',
+ ].join('\n');
},
- props: {
- previewMarkdown: {
- type: Boolean,
- required: true,
- },
+ },
+ 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);
+ },
+ methods: {
+ isValid(form) {
+ return (
+ !form ||
+ (form.find('.js-vue-markdown-field').length && $(this.$el).closest('form')[0] === form[0])
+ );
},
- computed: {
- mdTable() {
- return [
- '| header | header |',
- '| ------ | ------ |',
- '| cell | cell |',
- '| cell | cell |',
- ].join('\n');
- },
- },
- 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);
- },
- methods: {
- isValid(form) {
- return !form ||
- form.find('.js-vue-markdown-field').length &&
- $(this.$el).closest('form')[0] === form[0];
- },
- previewMarkdownTab(event, form) {
- if (event.target.blur) event.target.blur();
- if (!this.isValid(form)) return;
+ previewMarkdownTab(event, form) {
+ if (event.target.blur) event.target.blur();
+ if (!this.isValid(form)) return;
- this.$emit('preview-markdown');
- },
+ this.$emit('preview-markdown');
+ },
- writeMarkdownTab(event, form) {
- if (event.target.blur) event.target.blur();
- if (!this.isValid(form)) return;
+ writeMarkdownTab(event, form) {
+ if (event.target.blur) event.target.blur();
+ if (!this.isValid(form)) return;
- this.$emit('write-markdown');
- },
+ this.$emit('write-markdown');
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index c45dafa9807..feb7b8f227e 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -1,32 +1,32 @@
<script>
- import { Link } from '@gitlab-org/gitlab-ui';
+import { Link } from '@gitlab-org/gitlab-ui';
- export default {
- components: {
- 'gl-link': Link,
+export default {
+ components: {
+ 'gl-link': Link,
+ },
+ props: {
+ markdownDocsPath: {
+ type: String,
+ required: true,
},
- props: {
- markdownDocsPath: {
- type: String,
- required: true,
- },
- quickActionsDocsPath: {
- type: String,
- required: false,
- default: '',
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
+ quickActionsDocsPath: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- hasQuickActionsDocsPath() {
- return this.quickActionsDocsPath !== '';
- },
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- };
+ },
+ computed: {
+ hasQuickActionsDocsPath() {
+ return this.quickActionsDocsPath !== '';
+ },
+ },
+};
</script>
<template>
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 bda33636369..3e89e1c1e75 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -1,44 +1,44 @@
<script>
- import tooltip from '../../directives/tooltip';
- import icon from '../icon.vue';
+import tooltip from '../../directives/tooltip';
+import icon from '../icon.vue';
- export default {
- components: {
- icon,
+export default {
+ components: {
+ icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ buttonTitle: {
+ type: String,
+ required: true,
},
- directives: {
- tooltip,
+ icon: {
+ type: String,
+ required: true,
},
- props: {
- buttonTitle: {
- type: String,
- required: true,
- },
- icon: {
- type: String,
- required: true,
- },
- tag: {
- type: String,
- required: true,
- },
- tagBlock: {
- type: String,
- required: false,
- default: '',
- },
- tagSelect: {
- type: String,
- required: false,
- default: '',
- },
- prepend: {
- type: Boolean,
- required: false,
- default: false,
- },
+ tag: {
+ type: String,
+ required: true,
},
- };
+ tagBlock: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ tagSelect: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ prepend: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.vue b/app/assets/javascripts/vue_shared/components/memory_graph.vue
index 552a92541be..964dedb38c4 100644
--- a/app/assets/javascripts/vue_shared/components/memory_graph.vue
+++ b/app/assets/javascripts/vue_shared/components/memory_graph.vue
@@ -41,7 +41,8 @@ export default {
// Find metric timestamp which is closest to deploymentTime
timestampDiff = Math.abs(metricTimestamps[0] - median);
metricTimestamps.forEach((timestamp, index) => {
- if (index === 0) { // Skip first element
+ if (index === 0) {
+ // Skip first element
return;
}
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 38115f268bb..dcad79e521d 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
@@ -1,41 +1,39 @@
<script>
- /**
- * Common component to render a placeholder note and user information.
- *
- * This component needs to be used with a vuex store.
- * That vuex store needs to have a `getUserData` getter that contains
- * {
- * path: String,
- * avatar_url: String,
- * name: String,
- * username: String,
- * }
- *
- * @example
- * <placeholder-note
- * :note="{body: 'This is a note'}"
- * />
- */
- import { mapGetters } from 'vuex';
- import userAvatarLink from '../user_avatar/user_avatar_link.vue';
+/**
+ * Common component to render a placeholder note and user information.
+ *
+ * This component needs to be used with a vuex store.
+ * That vuex store needs to have a `getUserData` getter that contains
+ * {
+ * path: String,
+ * avatar_url: String,
+ * name: String,
+ * username: String,
+ * }
+ *
+ * @example
+ * <placeholder-note
+ * :note="{body: 'This is a note'}"
+ * />
+ */
+import { mapGetters } from 'vuex';
+import userAvatarLink from '../user_avatar/user_avatar_link.vue';
- export default {
- name: 'PlaceholderNote',
- components: {
- userAvatarLink,
+export default {
+ name: 'PlaceholderNote',
+ components: {
+ userAvatarLink,
+ },
+ props: {
+ note: {
+ type: Object,
+ required: true,
},
- props: {
- note: {
- type: Object,
- required: true,
- },
- },
- computed: {
- ...mapGetters([
- 'getUserData',
- ]),
- },
- };
+ },
+ computed: {
+ ...mapGetters(['getUserData']),
+ },
+};
</script>
<template>
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 95e2b38e292..674f923478d 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
@@ -1,21 +1,21 @@
<script>
- /**
- * Common component to render a placeholder system note.
- *
- * @example
- * <placeholder-system-note
- * :note="{ body: 'Commands are being applied'}"
- * />
- */
- export default {
- name: 'PlaceholderSystemNote',
- props: {
- note: {
- type: Object,
- required: true,
- },
+/**
+ * Common component to render a placeholder system note.
+ *
+ * @example
+ * <placeholder-system-note
+ * :note="{ body: 'Commands are being applied'}"
+ * />
+ */
+export default {
+ name: 'PlaceholderSystemNote',
+ props: {
+ note: {
+ type: Object,
+ required: true,
},
- };
+ },
+};
</script>
<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 2122d0a508e..de3c7a80365 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -109,7 +109,7 @@ export default {
class="system-note-commit-list-toggler flex-row"
@click="expanded = !expanded"
>
- <Icon
+ <icon
:name="toggleIcon"
:size="8"
class="append-right-5"
diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
index 7947ae1e4da..bf736a378dd 100644
--- a/app/assets/javascripts/vue_shared/components/panel_resizer.vue
+++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
@@ -1,90 +1,90 @@
<script>
- 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,
- },
+export default {
+ props: {
+ startSize: {
+ type: Number,
+ required: true,
},
- data() {
- return {
- size: this.startSize,
- };
+ side: {
+ type: String,
+ required: true,
},
- computed: {
- className() {
- return `drag-${this.side}`;
- },
- cursorStyle() {
- if (this.enabled) {
- return { cursor: 'ew-resize' };
- }
- return {};
- },
+ minSize: {
+ type: Number,
+ required: false,
+ default: 0,
},
- methods: {
- resetSize(e) {
- e.preventDefault();
- this.$emit('resize-start', this.size);
+ 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.$emit('resize-start', this.size);
- this.size = this.startSize;
- this.$emit('update:size', this.size);
+ this.size = this.startSize;
+ this.$emit('update:size', this.size);
- // End resizing on next tick so that listeners can react to DOM changes
- this.$nextTick(() => {
- this.$emit('resize-end', 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) {
+ // End resizing on next tick so that listeners can react to DOM changes
+ this.$nextTick(() => {
+ this.$emit('resize-end', this.size);
+ });
+ },
+ startDrag(e) {
+ if (this.enabled) {
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.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>
diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue
index bfeece12077..782d8e3abf6 100644
--- a/app/assets/javascripts/vue_shared/components/pikaday.vue
+++ b/app/assets/javascripts/vue_shared/components/pikaday.vue
@@ -1,62 +1,62 @@
<script>
- import Pikaday from 'pikaday';
- import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix';
+import Pikaday from 'pikaday';
+import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix';
- export default {
- name: 'DatePicker',
- props: {
- label: {
- type: String,
- required: false,
- default: 'Date picker',
- },
- selectedDate: {
- type: Date,
- required: false,
- default: null,
- },
- minDate: {
- type: Date,
- required: false,
- default: null,
- },
- maxDate: {
- type: Date,
- required: false,
- default: null,
- },
+export default {
+ name: 'DatePicker',
+ props: {
+ label: {
+ type: String,
+ required: false,
+ default: 'Date picker',
},
- mounted() {
- this.calendar = new Pikaday({
- field: this.$el.querySelector('.dropdown-menu-toggle'),
- theme: 'gitlab-theme animate-picker',
- format: 'yyyy-mm-dd',
- container: this.$el,
- defaultDate: this.selectedDate,
- setDefaultDate: !!this.selectedDate,
- minDate: this.minDate,
- maxDate: this.maxDate,
- parse: dateString => parsePikadayDate(dateString),
- toString: date => pikadayToString(date),
- onSelect: this.selected.bind(this),
- onClose: this.toggled.bind(this),
- });
-
- this.$el.append(this.calendar.el);
- this.calendar.show();
+ selectedDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ minDate: {
+ type: Date,
+ required: false,
+ default: null,
},
- beforeDestroy() {
- this.calendar.destroy();
+ maxDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ },
+ mounted() {
+ this.calendar = new Pikaday({
+ field: this.$el.querySelector('.dropdown-menu-toggle'),
+ theme: 'gitlab-theme animate-picker',
+ format: 'yyyy-mm-dd',
+ container: this.$el,
+ defaultDate: this.selectedDate,
+ setDefaultDate: !!this.selectedDate,
+ minDate: this.minDate,
+ maxDate: this.maxDate,
+ parse: dateString => parsePikadayDate(dateString),
+ toString: date => pikadayToString(date),
+ onSelect: this.selected.bind(this),
+ onClose: this.toggled.bind(this),
+ });
+
+ this.$el.append(this.calendar.el);
+ this.calendar.show();
+ },
+ beforeDestroy() {
+ this.calendar.destroy();
+ },
+ methods: {
+ selected(dateText) {
+ this.$emit('newDateSelected', this.calendar.toString(dateText));
},
- methods: {
- selected(dateText) {
- this.$emit('newDateSelected', this.calendar.toString(dateText));
- },
- toggled() {
- this.$emit('hidePicker');
- },
+ toggled() {
+ this.$emit('hidePicker');
},
- };
+ },
+};
</script>
<template>
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 97ca4d93bd7..1a2fd2ad985 100644
--- a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
+++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
@@ -1,6 +1,5 @@
<script>
-
- /* This is a re-usable vue component for rendering a project avatar that
+/* 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.
@@ -16,70 +15,70 @@
*/
- 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',
- directives: {
- tooltip,
+export default {
+ name: 'ProjectAvatarImage',
+ directives: {
+ tooltip,
+ },
+ 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',
+ },
+ },
+ 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;
},
- 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',
- },
+ tooltipContainer() {
+ return this.tooltipText ? 'body' : null;
},
- 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}`;
- },
+ avatarSizeClass() {
+ return `s${this.size}`;
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
index a2a9a5e6987..09394847b10 100644
--- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -1,67 +1,67 @@
<script>
- import DeprecatedModal from './deprecated_modal.vue';
+import DeprecatedModal from './deprecated_modal.vue';
- export default {
- name: 'RecaptchaModal',
+export default {
+ name: 'RecaptchaModal',
- components: {
- DeprecatedModal,
- },
+ components: {
+ DeprecatedModal,
+ },
- props: {
- html: {
- type: String,
- required: false,
- default: '',
- },
+ props: {
+ html: {
+ type: String,
+ required: false,
+ default: '',
},
+ },
- data() {
- return {
- script: {},
- scriptSrc: 'https://www.google.com/recaptcha/api.js',
- };
- },
+ data() {
+ return {
+ script: {},
+ scriptSrc: 'https://www.google.com/recaptcha/api.js',
+ };
+ },
- watch: {
- html() {
- this.appendRecaptchaScript();
- },
+ watch: {
+ html() {
+ this.appendRecaptchaScript();
},
+ },
- mounted() {
- window.recaptchaDialogCallback = this.submit.bind(this);
- },
+ mounted() {
+ window.recaptchaDialogCallback = this.submit.bind(this);
+ },
- methods: {
- appendRecaptchaScript() {
- this.removeRecaptchaScript();
+ methods: {
+ appendRecaptchaScript() {
+ this.removeRecaptchaScript();
- const script = document.createElement('script');
- script.src = this.scriptSrc;
- script.classList.add('js-recaptcha-script');
- script.async = true;
- script.defer = true;
+ const script = document.createElement('script');
+ script.src = this.scriptSrc;
+ script.classList.add('js-recaptcha-script');
+ script.async = true;
+ script.defer = true;
- this.script = script;
+ this.script = script;
- document.body.appendChild(script);
- },
+ document.body.appendChild(script);
+ },
- removeRecaptchaScript() {
- if (this.script instanceof Element) this.script.remove();
- },
+ removeRecaptchaScript() {
+ if (this.script instanceof Element) this.script.remove();
+ },
- close() {
- this.removeRecaptchaScript();
- this.$emit('close');
- },
+ close() {
+ this.removeRecaptchaScript();
+ this.$emit('close');
+ },
- submit() {
- this.$el.querySelector('form').submit();
- },
+ submit() {
+ this.$el.querySelector('form').submit();
},
- };
+ },
+};
</script>
<template>
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 9d757b27edc..500586302cf 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
@@ -1,96 +1,96 @@
<script>
- import datePicker from '../pikaday.vue';
- import toggleSidebar from './toggle_sidebar.vue';
- import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
- import { dateInWords } from '../../../lib/utils/datetime_utility';
+import datePicker from '../pikaday.vue';
+import toggleSidebar from './toggle_sidebar.vue';
+import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
+import { dateInWords } from '../../../lib/utils/datetime_utility';
- export default {
- name: 'SidebarDatePicker',
- components: {
- datePicker,
- toggleSidebar,
- collapsedCalendarIcon,
- },
- props: {
- blockClass: {
- type: String,
- required: false,
- default: '',
- },
- collapsed: {
- type: Boolean,
- required: false,
- default: true,
- },
- showToggleSidebar: {
- type: Boolean,
- required: false,
- default: false,
- },
- isLoading: {
- type: Boolean,
- required: false,
- default: false,
- },
- editable: {
- type: Boolean,
- required: false,
- default: false,
- },
- label: {
- type: String,
- required: false,
- default: 'Date picker',
- },
- selectedDate: {
- type: Date,
- required: false,
- default: null,
- },
- minDate: {
- type: Date,
- required: false,
- default: null,
- },
- maxDate: {
- type: Date,
- required: false,
- default: null,
- },
- },
- data() {
- return {
- editing: false,
- };
- },
- computed: {
- selectedAndEditable() {
- return this.selectedDate && this.editable;
- },
- selectedDateWords() {
- return dateInWords(this.selectedDate, true);
- },
- collapsedText() {
- return this.selectedDateWords ? this.selectedDateWords : 'None';
- },
- },
- methods: {
- stopEditing() {
- this.editing = false;
- },
- toggleDatePicker() {
- this.editing = !this.editing;
- },
- newDateSelected(date = null) {
- this.date = date;
- this.editing = false;
- this.$emit('saveDate', date);
- },
- toggleSidebar() {
- this.$emit('toggleCollapse');
- },
- },
- };
+export default {
+ name: 'SidebarDatePicker',
+ components: {
+ datePicker,
+ toggleSidebar,
+ collapsedCalendarIcon,
+ },
+ props: {
+ blockClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ collapsed: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ showToggleSidebar: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ editable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ label: {
+ type: String,
+ required: false,
+ default: 'Date picker',
+ },
+ selectedDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ minDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ maxDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ editing: false,
+ };
+ },
+ computed: {
+ selectedAndEditable() {
+ return this.selectedDate && this.editable;
+ },
+ selectedDateWords() {
+ return dateInWords(this.selectedDate, true);
+ },
+ collapsedText() {
+ return this.selectedDateWords ? this.selectedDateWords : 'None';
+ },
+ },
+ methods: {
+ stopEditing() {
+ this.editing = false;
+ },
+ toggleDatePicker() {
+ this.editing = !this.editing;
+ },
+ newDateSelected(date = null) {
+ this.date = date;
+ this.editing = false;
+ this.$emit('saveDate', date);
+ },
+ toggleSidebar() {
+ this.$emit('toggleCollapse');
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 8e9621c956f..03a5a078879 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -1,17 +1,17 @@
<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: {
- /**
+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:
@@ -20,12 +20,12 @@
gl.utils.visitUrl(`?page=${pagenum}`);
},
*/
- 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
@@ -41,94 +41,94 @@
previousPage: +headers['X-Prev-Page'],
});
*/
- pageInfo: {
- type: Object,
- required: true,
- },
+ pageInfo: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ prev() {
+ return this.pageInfo.previousPage;
+ },
+ next() {
+ return this.pageInfo.nextPage;
+ },
+ getItems() {
+ const total = this.pageInfo.totalPages;
+ const { page } = this.pageInfo;
+ 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;
},
- computed: {
- prev() {
- return this.pageInfo.previousPage;
- },
- next() {
- return this.pageInfo.nextPage;
- },
- getItems() {
- const total = this.pageInfo.totalPages;
- const { page } = this.pageInfo;
- 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;
- },
+ },
+ 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;
+ }
},
- 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;
- }
- },
- hideOnSmallScreen(item) {
- return !item.first && !item.last && !item.next && !item.prev && !item.active;
- },
+ hideOnSmallScreen(item) {
+ return !item.first && !item.last && !item.next && !item.prev && !item.active;
},
- };
+ },
+};
</script>
<template>
<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 368eeb6c453..d760263929a 100644
--- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -11,9 +11,7 @@ export default {
directives: {
tooltip,
},
- mixins: [
- timeagoMixin,
- ],
+ mixins: [timeagoMixin],
props: {
time: {
type: String,
diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue
index 5b9c51786d6..4e9289cbed8 100644
--- a/app/assets/javascripts/vue_shared/components/toggle_button.vue
+++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue
@@ -1,60 +1,60 @@
<script>
- import { s__ } from '../../locale';
- import icon from './icon.vue';
+import { s__ } from '../../locale';
+import icon from './icon.vue';
- const ICON_ON = 'status_success_borderless';
- const ICON_OFF = 'status_failed_borderless';
- const LABEL_ON = s__('ToggleButton|Toggle Status: ON');
- const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF');
+const ICON_ON = 'status_success_borderless';
+const ICON_OFF = 'status_failed_borderless';
+const LABEL_ON = s__('ToggleButton|Toggle Status: ON');
+const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF');
- export default {
- components: {
- icon,
- },
+export default {
+ components: {
+ icon,
+ },
- model: {
- prop: 'value',
- event: 'change',
- },
+ model: {
+ prop: 'value',
+ event: 'change',
+ },
- props: {
- name: {
- type: String,
- required: false,
- default: null,
- },
- value: {
- type: Boolean,
- required: false,
- default: null,
- },
- disabledInput: {
- type: Boolean,
- required: false,
- default: false,
- },
- isLoading: {
- type: Boolean,
- required: false,
- default: false,
- },
+ props: {
+ name: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ value: {
+ type: Boolean,
+ required: false,
+ default: null,
},
+ disabledInput: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
- computed: {
- toggleIcon() {
- return this.value ? ICON_ON : ICON_OFF;
- },
- ariaLabel() {
- return this.value ? LABEL_ON : LABEL_OFF;
- },
+ computed: {
+ toggleIcon() {
+ return this.value ? ICON_ON : ICON_OFF;
+ },
+ ariaLabel() {
+ return this.value ? LABEL_ON : LABEL_OFF;
},
+ },
- methods: {
- toggleFeature() {
- if (!this.disabledInput) this.$emit('change', !this.value);
- },
+ methods: {
+ toggleFeature() {
+ if (!this.disabledInput) this.$emit('change', !this.value);
},
- };
+ },
+};
</script>
<template>
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 ee3157bcb1b..14cb44b8619 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
@@ -1,5 +1,4 @@
<script>
-
/* This is a re-usable vue component for rendering a user avatar wrapped in
a clickable link (likely to the user's profile). The link, image, and
tooltip can be configured by props passed to this component.
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 ef3b16edf5f..8e460566d09 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
@@ -1,5 +1,4 @@
<script>
-
/* This is a re-usable vue component for rendering a user avatar svg (typically
for a blank state). It will receive styles comparable to the user avatar,
but no image is loaded, it isn't wrapped in a link, and tooltips aren't supported.
@@ -42,4 +41,3 @@ export default {
v-html="svg"
/>
</template>
-
diff --git a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
index f94cc670edf..67a1632269e 100644
--- a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
+++ b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
@@ -4,10 +4,7 @@
*
* Components need to have `scope`, `page` and `requestData`
*/
-import {
- historyPushState,
- buildUrlWithCurrentLocation,
-} from '../../lib/utils/common_utils';
+import { historyPushState, buildUrlWithCurrentLocation } from '../../lib/utils/common_utils';
export default {
methods: {
@@ -24,12 +21,14 @@ export default {
// stop polling
this.poll.stop();
- const queryString = Object.keys(parameters).map((parameter) => {
- const value = parameters[parameter];
- // update internal state for UI
- this[parameter] = value;
- return `${parameter}=${encodeURIComponent(value)}`;
- }).join('&');
+ const queryString = Object.keys(parameters)
+ .map(parameter => {
+ const value = parameters[parameter];
+ // update internal state for UI
+ this[parameter] = value;
+ return `${parameter}=${encodeURIComponent(value)}`;
+ })
+ .join('&');
// update polling parameters
this.requestData = parameters;
diff --git a/app/assets/javascripts/vue_shared/models/label.js b/app/assets/javascripts/vue_shared/models/label.js
index d29c7fe973a..2d2732d0661 100644
--- a/app/assets/javascripts/vue_shared/models/label.js
+++ b/app/assets/javascripts/vue_shared/models/label.js
@@ -6,7 +6,7 @@ export default class ListLabel {
this.color = obj.color;
this.textColor = obj.text_color;
this.description = obj.description;
- this.priority = (obj.priority !== null) ? obj.priority : Infinity;
+ this.priority = obj.priority !== null ? obj.priority : Infinity;
}
}
diff --git a/app/assets/javascripts/vue_shared/translate.js b/app/assets/javascripts/vue_shared/translate.js
index 48c63373b77..e0baf03acc3 100644
--- a/app/assets/javascripts/vue_shared/translate.js
+++ b/app/assets/javascripts/vue_shared/translate.js
@@ -1,11 +1,6 @@
-import {
- __,
- n__,
- s__,
- sprintf,
-} from '../locale';
+import { __, n__, s__, sprintf } from '../locale';
-export default (Vue) => {
+export default Vue => {
Vue.mixin({
methods: {
/**
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
index b9693892f45..754025207c8 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
@@ -21,7 +21,7 @@ Vue.http.interceptors.push((request, next) => {
Vue.http.interceptors.push((request, next) => {
request.headers.set(csrf.headerKey, csrf.token);
- next((response) => {
+ next(response => {
// Headers object has a `forEach` property that iterates through all values.
const headers = {};
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index ffe65ce780e..bd1cca69c03 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -17,7 +17,7 @@
*/
@import "../../../node_modules/pikaday/scss/pikaday";
-@import "../../../node_modules/dropzone/dist/basic.css";
+@import "../../../node_modules/dropzone/dist/basic";
/*
* GitLab UI framework
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 7145a76db6d..f26b1fddae5 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -283,18 +283,20 @@
.dismiss-button {
position: absolute;
- right: 6px;
- top: 6px;
+ right: $gl-padding-8;
+ top: $gl-padding-8;
cursor: pointer;
- color: $blue-300;
+ color: $blue-500;
z-index: 1;
border: 0;
background-color: transparent;
+ padding: $gl-padding-8;
+ line-height: 0;
&:hover,
&:focus {
border: 0;
- color: $blue-400;
+ color: $blue-700;
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 8603714f709..cdfad30e7ca 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -144,6 +144,13 @@
top: 11px;
right: 8px;
}
+
+ .ic-chevron-down {
+ position: absolute;
+ top: $gl-padding-8;
+ right: $gl-padding-8;
+ color: $gray-darkest;
+ }
}
@mixin dropdown-item-hover {
@@ -561,6 +568,10 @@
top: -1px;
}
+.dropdown-menu-close-icon {
+ vertical-align: middle;
+}
+
.dropdown-menu-back {
left: 7px;
top: 2px;
@@ -572,9 +583,10 @@
padding: 0 10px;
.fa,
- .input-icon {
+ .input-icon,
+ .ic-search {
position: absolute;
- top: 10px;
+ top: $gl-padding-8;
right: 20px;
color: $dropdown-input-fa-color;
font-size: 12px;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index be41dbfc61f..1c84baf68ed 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -12,6 +12,15 @@
max-width: $max-width;
}
+/**
+ * Mixin for fixed width container
+ */
+@mixin fixed-width-container {
+ max-width: $limited-layout-width - ($gl-padding * 2);
+ margin-left: auto;
+ margin-right: auto;
+}
+
/*
* Mixin for markdown tables
*/
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b7a95f604b8..0fde6e18cc7 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -605,6 +605,7 @@ $perf-bar-development: #4c1210;
$perf-bar-bucket-bg: #111;
$perf-bar-bucket-box-shadow-from: rgba($white-light, 0.2);
$perf-bar-bucket-box-shadow-to: rgba($black, 0.25);
+$perf-bar-canary-text: $orange-400;
/*
Issuable warning
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index b3c5c693824..54fbd40cece 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -305,8 +305,7 @@
}
.confidential-icon {
- position: relative;
- top: 1px;
+ vertical-align: text-top;
margin-right: 5px;
}
}
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index f0228768b5a..ec2108b15be 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -21,7 +21,7 @@
}
}
- svg {
+ .svg-container svg {
width: 136px;
height: 136px;
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 17b02c6e31e..8d884ad6891 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1013,6 +1013,7 @@
.with-performance-bar & {
top: 135px;
+ max-height: calc(100vh - 135px);
}
}
@@ -1046,3 +1047,19 @@
left: auto;
line-height: 0;
}
+
+@media (max-width: map-get($grid-breakpoints, md)-1) {
+ .diffs .files {
+ @include fixed-width-container;
+ flex-direction: column;
+
+ .diff-tree-list {
+ width: 100%;
+ }
+
+ .tree-list-holder {
+ max-height: calc(50px + 50vh);
+ padding-right: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 79984c1a546..19a36061c45 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -90,6 +90,7 @@
margin-right: 3px;
color: $gl-text-color-secondary;
display: inline-block;
+ vertical-align: text-top;
.fa:nth-child(1) {
margin-right: 3px;
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index fe792a53b44..f0cb81e0bc3 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -191,7 +191,7 @@
}
}
- svg {
+ .svg-container svg {
width: 62px;
height: 50px;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 62a9f97caa9..00b06aea898 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -1,8 +1,6 @@
// Limit MR description for side-by-side diff view
.fixed-width-container {
- max-width: $limited-layout-width - ($gl-padding * 2);
- margin-left: auto;
- margin-right: auto;
+ @include fixed-width-container;
}
.issuable-warning-icon {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 45382d4ea43..895db89f289 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -201,7 +201,6 @@
.mr-widget-icon {
font-size: 22px;
- margin-right: $gl-btn-padding;
}
.ci-status-icon svg {
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index dbf8692d69b..ccfa4e00a5b 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -42,6 +42,10 @@
margin-top: 0;
}
+ .settings-title {
+ cursor: pointer;
+ }
+
button {
position: absolute;
top: 20px;
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 59fdbf31fe9..9c01a2f8bda 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -68,6 +68,10 @@
}
}
+ .current-host.canary {
+ color: $perf-bar-canary-text;
+ }
+
strong {
color: $white-light;
}
diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
index 44864f9c7d0..25cc241e5b0 100644
--- a/app/controllers/admin/health_check_controller.rb
+++ b/app/controllers/admin/health_check_controller.rb
@@ -3,12 +3,5 @@
class Admin::HealthCheckController < Admin::ApplicationController
def show
@errors = HealthCheck::Utils.process_checks(['standard'])
- @failing_storage_statuses = Gitlab::Git::Storage::Health.for_failing_storages
- end
-
- def reset_storage_health
- Gitlab::Git::Storage::FailureInfo.reset_all!
- redirect_to admin_health_check_path,
- notice: _('Git storage health information has been reset')
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index ec45e2813c5..eeabcc0c9bb 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,7 +12,9 @@ class ApplicationController < ActionController::Base
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
- include InvalidUTF8ErrorHandler
+ # this can be removed after switching to rails 5
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/51908
+ include InvalidUTF8ErrorHandler unless Gitlab.rails5?
before_action :authenticate_sessionless_user!
before_action :authenticate_user!
@@ -66,7 +68,7 @@ class ApplicationController < ActionController::Base
head :forbidden, retry_after: Gitlab::Auth::UniqueIpsLimiter.config.unique_ips_limit_time_window
end
- rescue_from Gitlab::Git::Storage::Inaccessible, GRPC::Unavailable, Gitlab::Git::CommandError do |exception|
+ rescue_from GRPC::Unavailable, Gitlab::Git::CommandError do |exception|
log_exception(exception)
headers['Retry-After'] = exception.retry_after if exception.respond_to?(:retry_after)
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
index ab4bc911e17..dc9a52f8da5 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class HealthController < ActionController::Base
- protect_from_forgery with: :exception, except: :storage_check, prepend: true
+ protect_from_forgery with: :exception, prepend: true
include RequiresWhitelistedMonitoringClient
CHECKS = [
@@ -25,15 +25,6 @@ class HealthController < ActionController::Base
render_check_results(results)
end
- def storage_check
- results = Gitlab::Git::Storage::Checker.check_all
-
- render json: {
- check_interval: Gitlab::CurrentSettings.current_application_settings.circuitbreaker_check_interval,
- results: results
- }
- end
-
private
def render_check_results(results)
diff --git a/app/controllers/koding_controller.rb b/app/controllers/koding_controller.rb
deleted file mode 100644
index 72aa9d4f17f..00000000000
--- a/app/controllers/koding_controller.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-class KodingController < ApplicationController
- before_action :check_integration!
- layout 'koding'
-
- def index
- path = File.join(Rails.root, 'doc/user/project/koding.md')
- @markdown = File.read(path)
- end
-
- private
-
- def check_integration!
- render_404 unless Gitlab::CurrentSettings.koding_enabled?
- end
-end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index d0f59aa8162..312e256ea6c 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -10,7 +10,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
before_action :authorize_update_build!, only: [:keep]
before_action :extract_ref_name_and_path
before_action :set_request_format, only: [:file]
- before_action :validate_artifacts!
+ before_action :validate_artifacts!, except: [:download]
before_action :entry, only: [:file]
def download
@@ -102,7 +102,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def artifacts_file
- @artifacts_file ||= build.artifacts_file_for_type(params[:file_type] || :archive)
+ @artifacts_file ||= build&.artifacts_file_for_type(params[:file_type] || :archive)
end
def entry
diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
index 46449a4aae9..7d4d566499c 100644
--- a/app/controllers/projects/build_artifacts_controller.rb
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -6,10 +6,10 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
before_action :authorize_read_build!
before_action :extract_ref_name_and_path
- before_action :validate_artifacts!
+ before_action :validate_artifacts!, except: [:download]
def download
- redirect_to download_project_job_artifacts_path(project, job)
+ redirect_to download_project_job_artifacts_path(project, job, params: request.query_parameters)
end
def browse
diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb
index c356f8d2987..bcea96bce94 100644
--- a/app/controllers/projects/clusters/applications_controller.rb
+++ b/app/controllers/projects/clusters/applications_controller.rb
@@ -2,31 +2,20 @@
class Projects::Clusters::ApplicationsController < Projects::ApplicationController
before_action :cluster
- before_action :application_class, only: [:create]
before_action :authorize_read_cluster!
before_action :authorize_create_cluster!, only: [:create]
- # rubocop: disable CodeReuse/ActiveRecord
def create
- application = @application_class.find_or_initialize_by(cluster: @cluster)
-
- if application.has_attribute?(:hostname)
- application.hostname = params[:hostname]
- end
-
- if application.respond_to?(:oauth_application)
- application.oauth_application = create_oauth_application(application)
- end
-
- application.save!
-
- Clusters::Applications::ScheduleInstallationService.new(project, current_user).execute(application)
+ Clusters::Applications::CreateService
+ .new(@cluster, current_user, create_cluster_application_params)
+ .execute(request)
head :no_content
+ rescue Clusters::Applications::CreateService::InvalidApplicationError
+ render_404
rescue StandardError
head :bad_request
end
- # rubocop: enable CodeReuse/ActiveRecord
private
@@ -34,18 +23,7 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll
@cluster ||= project.clusters.find(params[:id]) || render_404
end
- def application_class
- @application_class ||= Clusters::Cluster::APPLICATIONS[params[:application]] || render_404
- end
-
- def create_oauth_application(application)
- oauth_application_params = {
- name: params[:application],
- redirect_uri: application.callback_url,
- scopes: 'api read_user openid',
- owner: current_user
- }
-
- Applications::CreateService.new(current_user, oauth_application_params).execute(request)
+ def create_cluster_application_params
+ params.permit(:application, :hostname)
end
end
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index bcdbf48bb35..62adc66fb09 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -40,7 +40,7 @@ class Projects::ClustersController < Projects::ApplicationController
def update
Clusters::UpdateService
- .new(project, current_user, update_params)
+ .new(current_user, update_params)
.execute(cluster)
if cluster.valid?
@@ -73,8 +73,8 @@ class Projects::ClustersController < Projects::ApplicationController
def create_gcp
@gcp_cluster = ::Clusters::CreateService
- .new(project, current_user, create_gcp_cluster_params)
- .execute(token_in_session)
+ .new(current_user, create_gcp_cluster_params)
+ .execute(project: project, access_token: token_in_session)
if @gcp_cluster.persisted?
redirect_to project_cluster_path(project, @gcp_cluster)
@@ -89,8 +89,8 @@ class Projects::ClustersController < Projects::ApplicationController
def create_user
@user_cluster = ::Clusters::CreateService
- .new(project, current_user, create_user_cluster_params)
- .execute(token_in_session)
+ .new(current_user, create_user_cluster_params)
+ .execute(project: project, access_token: token_in_session)
if @user_cluster.persisted?
redirect_to project_cluster_path(project, @user_cluster)
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index d16240af404..5b70c69d7f4 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -56,10 +56,12 @@ class UsersController < ApplicationController
def projects
load_projects
+ skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination])
+
respond_to do |format|
format.html { render 'show' }
format.json do
- pager_json("shared/projects/_list", @projects.count, projects: @projects)
+ pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination)
end
end
end
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb
index 970efa79dfb..45d5591e81b 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -7,8 +7,9 @@ class BranchesFinder
end
def execute
- branches = @repository.branches_sorted_by(sort)
- filter_by_name(branches)
+ branches = repository.branches_sorted_by(sort)
+ branches = by_search(branches)
+ branches
end
private
@@ -23,11 +24,39 @@ class BranchesFinder
@params[:sort].presence || 'name'
end
- def filter_by_name(branches)
- if search
- branches.select { |branch| branch.name.upcase.include?(search.upcase) }
+ def by_search(branches)
+ return branches unless search
+
+ case search
+ when ->(v) { v.starts_with?('^') }
+ filter_branches_with_prefix(branches, search.slice(1..-1).upcase)
+ when ->(v) { v.ends_with?('$') }
+ filter_branches_with_suffix(branches, search.chop.upcase)
else
- branches
+ matches = filter_branches_by_name(branches, search.upcase)
+ set_exact_match_as_first_result(matches, search)
end
end
+
+ def filter_branches_with_prefix(branches, prefix)
+ branches.select { |branch| branch.name.upcase.starts_with?(prefix) }
+ end
+
+ def filter_branches_with_suffix(branches, suffix)
+ branches.select { |branch| branch.name.upcase.ends_with?(suffix) }
+ end
+
+ def filter_branches_by_name(branches, term)
+ branches.select { |branch| branch.name.upcase.include?(term) }
+ end
+
+ def set_exact_match_as_first_result(matches, term)
+ exact_match_index = find_exact_match_index(matches, term)
+ matches.insert(0, matches.delete_at(exact_match_index)) if exact_match_index
+ matches
+ end
+
+ def find_exact_match_index(matches, term)
+ matches.index { |branch| branch.name.casecmp(term) == 0 }
+ end
end
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index c2404412006..6ececcd4152 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -42,17 +42,7 @@ class ProjectsFinder < UnionFinder
init_collection
end
- collection = by_ids(collection)
- collection = by_personal(collection)
- collection = by_starred(collection)
- collection = by_trending(collection)
- collection = by_visibilty_level(collection)
- collection = by_tags(collection)
- collection = by_search(collection)
- collection = by_archived(collection)
- collection = by_custom_attributes(collection)
- collection = by_deleted_status(collection)
-
+ collection = filter_projects(collection)
sort(collection)
end
@@ -66,6 +56,21 @@ class ProjectsFinder < UnionFinder
end
end
+ # EE would override this to add more filters
+ def filter_projects(collection)
+ collection = by_ids(collection)
+ collection = by_personal(collection)
+ collection = by_starred(collection)
+ collection = by_trending(collection)
+ collection = by_visibilty_level(collection)
+ collection = by_tags(collection)
+ collection = by_search(collection)
+ collection = by_archived(collection)
+ collection = by_custom_attributes(collection)
+ collection = by_deleted_status(collection)
+ collection
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def collection_with_user
if owned_projects?
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 15cbfeea609..0c9f69b6714 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -7,7 +7,6 @@ module ApplicationSettingsHelper
:gravatar_enabled?,
:password_authentication_enabled_for_web?,
:akismet_enabled?,
- :koding_enabled?,
to: :'Gitlab::CurrentSettings.current_application_settings'
def user_oauth_applications?
@@ -108,37 +107,6 @@ module ApplicationSettingsHelper
options_for_select(options, selected)
end
- def circuitbreaker_failure_count_help_text
- health_link = link_to(s_('AdminHealthPageLink|health page'), admin_health_check_path)
- api_link = link_to(s_('CircuitBreakerApiLink|circuitbreaker api'), help_page_path("api/repository_storage_health"))
- message = _("The number of failures of after which GitLab will completely "\
- "prevent access to the storage. The number of failures can be "\
- "reset in the admin interface: %{link_to_health_page} or using "\
- "the %{api_documentation_link}.")
- message = message % { link_to_health_page: health_link, api_documentation_link: api_link }
-
- message.html_safe
- end
-
- def circuitbreaker_access_retries_help_text
- _('The number of attempts GitLab will make to access a storage.')
- end
-
- def circuitbreaker_failure_reset_time_help_text
- _("The time in seconds GitLab will keep failure information. When no "\
- "failures occur during this time, information about the mount is reset.")
- end
-
- def circuitbreaker_storage_timeout_help_text
- _("The time in seconds GitLab will try to access storage. After this time a "\
- "timeout error will be raised.")
- end
-
- def circuitbreaker_check_interval_help_text
- _("The time in seconds between storage checks. When a previous check did "\
- "complete yet, GitLab will skip a check.")
- end
-
def visible_attributes
[
:admin_notification_email,
@@ -150,11 +118,6 @@ module ApplicationSettingsHelper
:authorized_keys_enabled,
:auto_devops_enabled,
:auto_devops_domain,
- :circuitbreaker_access_retries,
- :circuitbreaker_check_interval,
- :circuitbreaker_failure_count_threshold,
- :circuitbreaker_failure_reset_time,
- :circuitbreaker_storage_timeout,
:clientside_sentry_dsn,
:clientside_sentry_enabled,
:container_registry_token_expire_delay,
@@ -191,8 +154,6 @@ module ApplicationSettingsHelper
:housekeeping_incremental_repack_period,
:html_emails_enabled,
:import_sources,
- :koding_enabled,
- :koding_url,
:max_artifacts_size,
:max_attachment_size,
:max_pages_size,
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 883e5ddff57..8d58c86b7a4 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -195,7 +195,7 @@ module BlobHelper
{
'relative-url-root' => Rails.application.config.relative_url_root,
'assets-prefix' => Gitlab::Application.config.assets.prefix,
- 'blob-language' => @blob && @blob.language.try(:ace_mode),
+ 'blob-filename' => @blob && @blob.path,
'project-id' => project.id
}
end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 33c53021c11..463f4145bdd 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -21,29 +21,6 @@ module DashboardHelper
links.any? { |link| dashboard_nav_link?(link) }
end
- def controller_action_to_child_dashboards(controller = controller_name, action = action_name)
- case "#{controller}##{action}"
- when 'projects#index', 'root#index', 'projects#starred', 'projects#trending'
- %w(projects stars)
- when 'dashboard#activity'
- %w(starred_project_activity project_activity)
- when 'groups#index'
- %w(groups)
- when 'todos#index'
- %w(todos)
- when 'dashboard#issues'
- %w(issues)
- when 'dashboard#merge_requests'
- %w(merge_requests)
- else
- []
- end
- end
-
- def user_default_dashboard?(user = current_user)
- controller_action_to_child_dashboards.any? {|dashboard| dashboard == user.dashboard }
- end
-
private
def get_dashboard_nav_links
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index ff9842d4cd9..f4f46b0fe96 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -18,22 +18,20 @@ module PreferencesHelper
groups: _("Your Groups"),
todos: _("Your Todos"),
issues: _("Assigned Issues"),
- merge_requests: _("Assigned Merge Requests")
+ merge_requests: _("Assigned Merge Requests"),
+ operations: _("Operations Dashboard")
}.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text
def dashboard_choices
- defined = User.dashboards
+ dashboards = User.dashboards.keys
- if defined.size != DASHBOARD_CHOICES.size
- # Ensure that anyone adding new options updates this method too
- raise "`User` defines #{defined.size} dashboard choices," \
- " but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
- else
- defined.map do |key, _|
- # Use `fetch` so `KeyError` gets raised when a key is missing
- [DASHBOARD_CHOICES.fetch(key), key]
- end
+ validate_dashboard_choices!(dashboards)
+ dashboards -= excluded_dashboard_choices
+
+ dashboards.map do |key|
+ # Use `fetch` so `KeyError` gets raised when a key is missing
+ [DASHBOARD_CHOICES.fetch(key), key]
end
end
@@ -52,4 +50,20 @@ module PreferencesHelper
def user_color_scheme
Gitlab::ColorSchemes.for_user(current_user).css_class
end
+
+ private
+
+ # Ensure that anyone adding new options updates `DASHBOARD_CHOICES` too
+ def validate_dashboard_choices!(user_dashboards)
+ if user_dashboards.size != DASHBOARD_CHOICES.size
+ raise "`User` defines #{user_dashboards.size} dashboard choices," \
+ " but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
+ end
+ end
+
+ # List of dashboard choice to be excluded from CE.
+ # EE would override this.
+ def excluded_dashboard_choices
+ ['operations']
+ end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 0016f89db5c..d9713f9c9b0 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -388,22 +388,6 @@ module ProjectsHelper
end
end
- def koding_project_url(project = nil, branch = nil, sha = nil)
- if project
- import_path = "/Home/Stacks/import"
-
- repo = project.full_path
- branch ||= project.default_branch
- sha ||= project.commit.short_id
-
- path = "#{import_path}?repo=#{repo}&branch=#{branch}&sha=#{sha}"
-
- return URI.join(Gitlab::CurrentSettings.koding_url, path).to_s
- end
-
- Gitlab::CurrentSettings.koding_url
- end
-
def project_wiki_path_with_version(proj, page, version, is_newest)
url_params = is_newest ? {} : { version_id: version }
project_wiki_path(proj, page, url_params)
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 53bd43d4861..8ed2a2ec9f4 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -48,15 +48,21 @@ module SortingHelper
def groups_sort_options_hash
{
- sort_value_name => sort_title_name,
- sort_value_name_desc => sort_title_name_desc,
+ sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
sort_value_recently_created => sort_title_recently_created,
- sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_oldest_created => sort_title_oldest_created,
sort_value_recently_updated => sort_title_recently_updated,
- sort_value_oldest_updated => sort_title_oldest_updated
+ sort_value_oldest_updated => sort_title_oldest_updated
}
end
+ def subgroups_sort_options_hash
+ groups_sort_options_hash.merge(
+ sort_value_most_stars => sort_title_most_stars
+ )
+ end
+
def admin_groups_sort_options_hash
groups_sort_options_hash.merge(
sort_value_largest_group => sort_title_largest_group
diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb
deleted file mode 100644
index 182e8e6641b..00000000000
--- a/app/helpers/storage_health_helper.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module StorageHealthHelper
- def failing_storage_health_message(storage_health)
- storage_name = content_tag(:strong, h(storage_health.storage_name))
- host_names = h(storage_health.failing_on_hosts.to_sentence)
- translation_params = { storage_name: storage_name,
- host_names: host_names,
- failed_attempts: storage_health.total_failures }
-
- translation = n_('%{storage_name}: failed storage access attempt on host:',
- '%{storage_name}: %{failed_attempts} failed storage access attempts:',
- storage_health.total_failures) % translation_params
-
- translation.html_safe
- end
-
- def message_for_circuit_breaker(circuit_breaker)
- maximum_failures = circuit_breaker.failure_count_threshold
- current_failures = circuit_breaker.failure_count
-
- translation_params = { number_of_failures: current_failures,
- maximum_failures: maximum_failures }
-
- if circuit_breaker.circuit_broken?
- s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
- "retry automatically. Reset storage information when the problem is "\
- "resolved.") % translation_params
- else
- _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
- "allow access on the next attempt.") % translation_params
- end
- end
-end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index f7347ee61b4..662f3e00047 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -118,6 +118,7 @@ class Notify < BaseMailer
add_unsubscription_headers_and_links
headers["X-GitLab-#{model.class.name}-ID"] = model.id
+ headers["X-GitLab-#{model.class.name}-IID"] = model.iid if model.respond_to?(:iid)
headers['X-GitLab-Reply-Key'] = reply_key
@reason = headers['X-GitLab-NotificationReason']
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 65a2f760f93..b66ec0ffab6 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -4,6 +4,7 @@ class ApplicationSetting < ActiveRecord::Base
include CacheableAttributes
include CacheMarkdownField
include TokenAuthenticatable
+ include IgnorableColumn
add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
@@ -27,6 +28,14 @@ class ApplicationSetting < ActiveRecord::Base
serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
+ ignore_column :circuitbreaker_failure_count_threshold
+ ignore_column :circuitbreaker_failure_reset_time
+ ignore_column :circuitbreaker_storage_timeout
+ ignore_column :circuitbreaker_access_retries
+ ignore_column :circuitbreaker_check_interval
+ ignore_column :koding_url
+ ignore_column :koding_enabled
+
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
cache_markdown_field :shared_runners_text, pipeline: :plain_markdown
@@ -93,10 +102,6 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :unique_ips_limit_enabled
- validates :koding_url,
- presence: true,
- if: :koding_enabled
-
validates :plantuml_url,
presence: true,
if: :plantuml_enabled
@@ -150,17 +155,6 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { greater_than_or_equal_to: 0 }
- validates :circuitbreaker_failure_count_threshold,
- :circuitbreaker_failure_reset_time,
- :circuitbreaker_storage_timeout,
- :circuitbreaker_check_interval,
- presence: true,
- numericality: { only_integer: true, greater_than_or_equal_to: 0 }
-
- validates :circuitbreaker_access_retries,
- presence: true,
- numericality: { only_integer: true, greater_than_or_equal_to: 1 }
-
validates :gitaly_timeout_default,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
@@ -256,8 +250,6 @@ class ApplicationSetting < ActiveRecord::Base
housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10,
import_sources: Settings.gitlab['import_sources'],
- koding_enabled: false,
- koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
mirror_available: true,
diff --git a/app/models/blob.rb b/app/models/blob.rb
index acc64ffca67..31a839274b5 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -162,7 +162,7 @@ class Blob < SimpleDelegator
if stored_externally?
if rich_viewer
rich_viewer.binary?
- elsif Linguist::Language.find_by_extension(name).any?
+ elsif known_extension?
false
elsif _mime_type
_mime_type.binary?
diff --git a/app/models/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index 7be6a14f585..e43a0fd1786 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -19,7 +19,7 @@ module Clusters
def set_initial_status
return unless not_installable?
- if cluster&.application_ingress_installed? && cluster.application_ingress.external_ip
+ if cluster&.application_ingress_available? && cluster.application_ingress.external_ip
self.status = 'installable'
end
end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index d7011ef447a..20d53b8e620 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -43,8 +43,9 @@ module Clusters
delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true
delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true
- delegate :installed?, to: :application_helm, prefix: true, allow_nil: true
- delegate :installed?, to: :application_ingress, prefix: true, allow_nil: true
+ delegate :available?, to: :application_helm, prefix: true, allow_nil: true
+ delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
+ delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
enum platform_type: {
kubernetes: 1
diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb
index e3deedfb036..683b45331f6 100644
--- a/app/models/clusters/concerns/application_core.rb
+++ b/app/models/clusters/concerns/application_core.rb
@@ -15,7 +15,7 @@ module Clusters
def set_initial_status
return unless not_installable?
- self.status = 'installable' if cluster&.application_helm_installed?
+ self.status = 'installable' if cluster&.application_helm_available?
end
def self.application_name
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index a9df59fc059..93bdf9c223d 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -66,6 +66,10 @@ module Clusters
end
end
end
+
+ def available?
+ installed? || updated?
+ end
end
end
end
diff --git a/app/models/concerns/blob_like.rb b/app/models/concerns/blob_like.rb
index e96fefe81c4..f20f01486a5 100644
--- a/app/models/concerns/blob_like.rb
+++ b/app/models/concerns/blob_like.rb
@@ -2,7 +2,7 @@
module BlobLike
extend ActiveSupport::Concern
- include Linguist::BlobHelper
+ include Gitlab::BlobHelper
def id
raise NotImplementedError
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 6962b54441b..62dc0f2cbeb 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -19,6 +19,17 @@ class Deployment < ActiveRecord::Base
after_create :create_ref
after_create :invalidate_cache
+ scope :for_environment, -> (environment) { where(environment_id: environment) }
+
+ def self.last_for_environment(environment)
+ ids = self
+ .for_environment(environment)
+ .select('MAX(id) AS id')
+ .group(:environment_id)
+ .map(&:id)
+ find(ids)
+ end
+
def commit
project.commit(sha)
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 309bd4f37c9..0816c395185 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -48,6 +48,8 @@ class Environment < ActiveRecord::Base
order(Gitlab::Database.nulls_first_order("(#{max_deployment_id_sql})", 'ASC'))
end
scope :in_review_folder, -> { where(environment_type: "review") }
+ scope :for_name, -> (name) { where(name: name) }
+ scope :for_project, -> (project) { where(project_id: project) }
state_machine :state, initial: :available do
event :start do
diff --git a/app/models/project.rb b/app/models/project.rb
index 05e14c578b5..b80e41e4a96 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -49,8 +49,11 @@ class Project < ActiveRecord::Base
attachments: 2
}.freeze
- # Valids ports to import from
- VALID_IMPORT_PORTS = [22, 80, 443].freeze
+ VALID_IMPORT_PORTS = [80, 443].freeze
+ VALID_IMPORT_PROTOCOLS = %w(http https git).freeze
+
+ VALID_MIRROR_PORTS = [22, 80, 443].freeze
+ VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
cache_markdown_field :description, pipeline: :description
@@ -305,10 +308,10 @@ class Project < ActiveRecord::Base
validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id }
- validates :import_url, url: { protocols: %w(http https ssh git),
+ validates :import_url, url: { protocols: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
+ ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
allow_localhost: false,
- enforce_user: true,
- ports: VALID_IMPORT_PORTS }, if: [:external_import?, :import_url_changed?]
+ enforce_user: true }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
@@ -1789,7 +1792,7 @@ class Project < ActiveRecord::Base
return unless export_file_exists?
import_export_upload.remove_export_file!
- import_export_upload.save
+ import_export_upload.save unless import_export_upload.destroyed?
end
def export_file_exists?
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 509e5b6089b..f141502d5df 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -26,7 +26,7 @@ class PrometheusService < MonitoringService
end
def editable?
- manual_configuration? || !prometheus_installed?
+ manual_configuration? || !prometheus_available?
end
def title
@@ -75,17 +75,17 @@ class PrometheusService < MonitoringService
RestClient::Resource.new(api_url) if api_url && manual_configuration? && active?
end
- def prometheus_installed?
+ def prometheus_available?
return false if template?
return false unless project
- project.clusters.enabled.any? { |cluster| cluster.application_prometheus&.installed? }
+ project.clusters.enabled.any? { |cluster| cluster.application_prometheus_available? }
end
private
def synchronize_service_state
- self.active = prometheus_installed? || manual_configuration?
+ self.active = prometheus_available? || manual_configuration?
true
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a3a3ce179fc..6ce480c32c4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -36,7 +36,7 @@ class Repository
# For example, for entry `:commit_count` there's a method called `commit_count` which
# stores its data in the `commit_count` cache key.
CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
- changelog license_blob license_key gitignore koding_yml
+ changelog license_blob license_key gitignore
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? root_ref has_visible_content?
issue_template_names merge_request_template_names xcode_project?).freeze
@@ -53,7 +53,6 @@ class Repository
license: %i(license_blob license_key license),
contributing: :contribution_guide,
gitignore: :gitignore,
- koding: :koding_yml,
gitlab_ci: :gitlab_ci_yml,
avatar: :avatar,
issue_template: :issue_template_names,
@@ -619,11 +618,6 @@ class Repository
end
cache_method :gitignore
- def koding_yml
- file_on_head(:koding)
- end
- cache_method :koding_yml
-
def gitlab_ci_yml
file_on_head(:gitlab_ci)
end
@@ -881,10 +875,12 @@ class Repository
delegate :merged_branch_names, to: :raw_repository
- def merge_base(first_commit_id, second_commit_id)
- first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
- second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
- raw_repository.merge_base(first_commit_id, second_commit_id)
+ def merge_base(*commits_or_ids)
+ commit_ids = commits_or_ids.map do |commit_or_id|
+ commit_or_id.is_a?(::Commit) ? commit_or_id.id : commit_or_id
+ end
+
+ raw_repository.merge_base(*commit_ids)
end
def ancestor?(ancestor_id, descendant_id)
diff --git a/app/models/user.rb b/app/models/user.rb
index 8a7acfb73b1..a0665518cf5 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -217,7 +217,7 @@ class User < ActiveRecord::Base
# User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array.
- enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos, :issues, :merge_requests]
+ enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos, :issues, :merge_requests, :operations]
# User's Project preference
# Note: When adding an option, it MUST go on the end of the array.
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
index 2c0e8659fc1..1cd05cf3aac 100644
--- a/app/models/user_callout.rb
+++ b/app/models/user_callout.rb
@@ -6,8 +6,7 @@ class UserCallout < ActiveRecord::Base
enum feature_name: {
gke_cluster_integration: 1,
gcp_signup_offer: 2,
- cluster_security_warning: 3,
- gold_trial: 4
+ cluster_security_warning: 3
}
validates :user, presence: true
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index d2434d96fd7..79cd3606aec 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -36,8 +36,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
contribution_guide_anchor_data,
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
kubernetes_cluster_anchor_data,
- gitlab_ci_anchor_data,
- koding_anchor_data
+ gitlab_ci_anchor_data
].compact.reject { |item| item.enabled }
end
@@ -125,43 +124,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
add_special_file_path(file_name: 'README.md')
end
- def add_koding_stack_path
- project_new_blob_path(
- project,
- default_branch || 'master',
- file_name: '.koding.yml',
- commit_message: "Add Koding stack script",
- content: <<-CONTENT.strip_heredoc
- provider:
- aws:
- access_key: '${var.aws_access_key}'
- secret_key: '${var.aws_secret_key}'
- resource:
- aws_instance:
- #{project.path}-vm:
- instance_type: t2.nano
- user_data: |-
-
- # Created by GitLab UI for :>
-
- echo _KD_NOTIFY_@Installing Base packages...@
-
- apt-get update -y
- apt-get install git -y
-
- echo _KD_NOTIFY_@Cloning #{project.name}...@
-
- export KODING_USER=${var.koding_user_username}
- export REPO_URL=#{root_url}${var.koding_queryString_repo}.git
- export BRANCH=${var.koding_queryString_branch}
-
- sudo -i -u $KODING_USER git clone $REPO_URL -b $BRANCH
-
- echo _KD_NOTIFY_@#{project.name} cloned.@
- CONTENT
- )
- end
-
def license_short_name
license = repository.license
license&.nickname || license&.name || 'LICENSE'
@@ -310,14 +272,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
end
- def koding_anchor_data
- if current_user && can_current_user_push_code? && koding_enabled? && repository.koding_yml.blank?
- AnchorData.new(false,
- _('Set up Koding'),
- add_koding_stack_path)
- end
- end
-
def tags_to_show
project.tag_list.take(MAX_TAGS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
end
@@ -363,8 +317,4 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
branch_name: branch_name
)
end
-
- def koding_enabled?
- Gitlab::CurrentSettings.koding_enabled?
- end
end
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 3d508a9a407..066a5b1885c 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -4,11 +4,12 @@ class BuildDetailsEntity < JobEntity
expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags
expose :has_trace?, as: :has_trace
+ expose :stage
expose :user, using: UserEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
- expose :deployment_status, if: -> (*) { build.has_environment? } do
+ expose :deployment_status, if: -> (*) { build.starts_environment? } do
expose :deployment_status, as: :status
expose :persisted_environment, as: :environment, with: EnvironmentEntity
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 16a477c92fa..c3f7d4651fb 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -42,6 +42,6 @@ class IssueEntity < IssuableEntity
end
expose :preview_note_path do |issue|
- preview_markdown_path(issue.project, quick_actions_target_type: 'Issue', quick_actions_target_id: issue.id)
+ preview_markdown_path(issue.project, quick_actions_target_type: 'Issue', quick_actions_target_id: issue.iid)
end
end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 380e8804f51..9ec24f799ef 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -222,7 +222,7 @@ class MergeRequestWidgetEntity < IssuableEntity
end
expose :preview_note_path do |merge_request|
- preview_markdown_path(merge_request.project, quick_actions_target_type: 'MergeRequest', quick_actions_target_id: merge_request.id)
+ preview_markdown_path(merge_request.project, quick_actions_target_type: 'MergeRequest', quick_actions_target_id: merge_request.iid)
end
expose :merge_commit_path do |merge_request|
diff --git a/app/services/applications/create_service.rb b/app/services/applications/create_service.rb
index 3d88c4f064e..b6c30da4d3a 100644
--- a/app/services/applications/create_service.rb
+++ b/app/services/applications/create_service.rb
@@ -9,6 +9,7 @@ module Applications
end
# rubocop: enable CodeReuse/ActiveRecord
+ # EE would override and use `request` arg
def execute(request)
Doorkeeper::Application.create(@params)
end
diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb
new file mode 100644
index 00000000000..55f917798de
--- /dev/null
+++ b/app/services/clusters/applications/create_service.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class CreateService
+ InvalidApplicationError = Class.new(StandardError)
+
+ attr_reader :cluster, :current_user, :params
+
+ def initialize(cluster, user, params = {})
+ @cluster = cluster
+ @current_user = user
+ @params = params.dup
+ end
+
+ def execute(request)
+ create_application.tap do |application|
+ if application.has_attribute?(:hostname)
+ application.hostname = params[:hostname]
+ end
+
+ if application.respond_to?(:oauth_application)
+ application.oauth_application = create_oauth_application(application, request)
+ end
+
+ application.save!
+
+ Clusters::Applications::ScheduleInstallationService.new(application).execute
+ end
+ end
+
+ private
+
+ def create_application
+ builder.call(@cluster)
+ end
+
+ def builder
+ builders[application_name] || raise(InvalidApplicationError, "invalid application: #{application_name}")
+ end
+
+ def builders
+ {
+ "helm" => -> (cluster) { cluster.application_helm || cluster.build_application_helm },
+ "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress },
+ "prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus },
+ "runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner },
+ "jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter }
+ }
+ end
+
+ def application_name
+ params[:application]
+ end
+
+ def create_oauth_application(application, request)
+ oauth_application_params = {
+ name: params[:application],
+ redirect_uri: application.callback_url,
+ scopes: 'api read_user openid',
+ owner: current_user
+ }
+
+ ::Applications::CreateService.new(current_user, oauth_application_params).execute(request)
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/schedule_installation_service.rb b/app/services/clusters/applications/schedule_installation_service.rb
index 4ead4f619c8..d75ba70c27e 100644
--- a/app/services/clusters/applications/schedule_installation_service.rb
+++ b/app/services/clusters/applications/schedule_installation_service.rb
@@ -2,8 +2,14 @@
module Clusters
module Applications
- class ScheduleInstallationService < ::BaseService
- def execute(application)
+ class ScheduleInstallationService
+ attr_reader :application
+
+ def initialize(application)
+ @application = application
+ end
+
+ def execute
application.make_scheduled!
ClusterInstallAppWorker.perform_async(application.name, application.id)
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index e3e0cfa462c..c6e955800af 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -1,36 +1,34 @@
# frozen_string_literal: true
module Clusters
- class CreateService < BaseService
- attr_reader :access_token
+ class CreateService
+ attr_reader :current_user, :params
- def execute(access_token = nil)
- @access_token = access_token
+ def initialize(user = nil, params = {})
+ @current_user, @params = user, params.dup
+ end
- raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster?
+ def execute(project:, access_token: nil)
+ raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster?(project)
- create_cluster.tap do |cluster|
+ cluster_params = params.merge(user: current_user, projects: [project])
+ cluster_params[:provider_gcp_attributes].try do |provider|
+ provider[:access_token] = access_token
+ end
+
+ create_cluster(cluster_params).tap do |cluster|
ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
end
end
private
- def create_cluster
+ def create_cluster(cluster_params)
Clusters::Cluster.create(cluster_params)
end
- def cluster_params
- return @cluster_params if defined?(@cluster_params)
-
- params[:provider_gcp_attributes].try do |provider|
- provider[:access_token] = access_token
- end
-
- @cluster_params = params.merge(user: current_user, projects: [project])
- end
-
- def can_create_cluster?
+ # EE would override this method
+ def can_create_cluster?(project)
project.clusters.empty?
end
end
diff --git a/app/services/clusters/update_service.rb b/app/services/clusters/update_service.rb
index 98fdeec4fb1..25d26e761b1 100644
--- a/app/services/clusters/update_service.rb
+++ b/app/services/clusters/update_service.rb
@@ -1,7 +1,13 @@
# frozen_string_literal: true
module Clusters
- class UpdateService < BaseService
+ class UpdateService
+ attr_reader :current_user, :params
+
+ def initialize(user = nil, params = {})
+ @current_user, @params = user, params.dup
+ end
+
def execute(cluster)
cluster.update(params)
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 575678da1fa..729bc991294 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -211,7 +211,7 @@ module SystemNoteService
# "closed via bc17db76"
#
# Returns the created Note object
- def change_status(noteable, project, author, status, source)
+ def change_status(noteable, project, author, status, source = nil)
body = status.dup
body << " via #{source.gfm_reference(project)}" if source
diff --git a/app/views/admin/application_settings/_koding.html.haml b/app/views/admin/application_settings/_koding.html.haml
deleted file mode 100644
index 8b635b08abd..00000000000
--- a/app/views/admin/application_settings/_koding.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-koding-settings'), html: { class: 'fieldset-form' } do |f|
- = form_errors(@application_setting)
-
- %fieldset
- .form-group
- .form-check
- = f.check_box :koding_enabled, class: 'form-check-input'
- = f.label :koding_enabled, class: 'form-check-label' do
- Enable Koding
- .form-text.text-muted
- Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again.
- .form-group
- = f.label :koding_url, 'Koding URL', class: 'label-bold'
- = f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
- .form-text.text-muted
- Koding has integration enabled out of the box for the
- %strong gitlab
- team, and you need to provide that team's URL here. Learn more in the
- = succeed "." do
- = link_to "Koding administration documentation", help_page_path("administration/integration/koding")
-
- = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_repository_storage.html.haml b/app/views/admin/application_settings/_repository_storage.html.haml
index 908b30cc3ce..c6c29ed1f21 100644
--- a/app/views/admin/application_settings/_repository_storage.html.haml
+++ b/app/views/admin/application_settings/_repository_storage.html.haml
@@ -20,32 +20,5 @@
Manage repository storage paths. Learn more in the
= succeed "." do
= link_to "repository storages documentation", help_page_path("administration/repository_storage_paths")
- .sub-section
- %h4 Circuit breaker
- .form-group
- = f.label :circuitbreaker_check_interval, _('Check interval'), class: 'label-bold'
- = f.number_field :circuitbreaker_check_interval, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_check_interval_help_text
- .form-group
- = f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'label-bold'
- = f.number_field :circuitbreaker_access_retries, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_access_retries_help_text
- .form-group
- = f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'label-bold'
- = f.number_field :circuitbreaker_storage_timeout, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_storage_timeout_help_text
- .form-group
- = f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'label-bold'
- = f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_failure_count_help_text
- .form-group
- = f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'label-bold'
- = f.number_field :circuitbreaker_failure_reset_time, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_failure_reset_time_help_text
= f.submit 'Save changes', class: "btn btn-success qa-save-changes-button"
diff --git a/app/views/admin/application_settings/repository.html.haml b/app/views/admin/application_settings/repository.html.haml
index be13138a764..b50a0dd5a18 100644
--- a/app/views/admin/application_settings/repository.html.haml
+++ b/app/views/admin/application_settings/repository.html.haml
@@ -20,7 +20,7 @@
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
- = _('Configure storage path and circuit breaker settings.')
+ = _('Configure storage path settings.')
.settings-content
= render 'repository_storage'
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index 279db189a24..65e4723afe6 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -68,18 +68,6 @@
.settings-content
= render 'terms'
-- if koding_enabled?
- %section.settings.as-koding.no-animate#js-koding-settings{ class: ('expanded' if expanded_by_default?) }
- .settings-header
- %h4
- = _('Koding')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded_by_default? ? _('Collapse') : _('Expand')
- %p
- = _('Online IDE integration settings.')
- .settings-content
- = render 'koding'
-
= render_if_exists 'admin/application_settings/external_authorization_service_form', expanded: expanded_by_default?
%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded_by_default?) }
diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml
index e69143abe45..df3eeba907c 100644
--- a/app/views/admin/applications/show.html.haml
+++ b/app/views/admin/applications/show.html.haml
@@ -22,7 +22,7 @@
.input-group
%input.label.label-monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
.input-group-append
- = clipboard_button(target: '#application_id', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
+ = clipboard_button(target: '#secret', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
%tr
%td
= _('Callback URL')
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 2a117c1414e..5e05568e384 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -10,11 +10,11 @@
.col-sm-10
= render 'shared/choose_group_avatar_button', f: f
- = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
+ = render 'shared/old_visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
.form-group.row
.offset-sm-2.col-sm-10
- = render 'shared/allow_request_access', form: f
+ = render 'shared/allow_request_access', form: f, bold_label: true
= render 'groups/group_admin_settings', f: f
diff --git a/app/views/admin/health_check/_failing_storages.html.haml b/app/views/admin/health_check/_failing_storages.html.haml
deleted file mode 100644
index 6830201538d..00000000000
--- a/app/views/admin/health_check/_failing_storages.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-- if failing_storages.any?
- = _('There are problems accessing Git storage: ')
- %ul
- - failing_storages.each do |storage_health|
- %li
- = failing_storage_health_message(storage_health)
- %ul
- - storage_health.failing_circuit_breakers.each do |circuit_breaker|
- %li
- #{circuit_breaker.hostname}: #{message_for_circuit_breaker(circuit_breaker)}
-
- = _("Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again.")
- .prepend-top-10
- = button_to _("Reset git storage health information"), reset_storage_health_admin_health_check_path,
- method: :post, class: 'btn btn-default'
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
index d51ac854b04..0f5e97e288a 100644
--- a/app/views/admin/health_check/show.html.haml
+++ b/app/views/admin/health_check/show.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
- page_title _('Health Check')
-- no_errors = @errors.blank? && @failing_storage_statuses.blank?
+- no_errors = @errors.blank?
%div{ class: container_class }
%h3.page-title= page_title
@@ -39,4 +39,3 @@
#{ s_('HealthCheck|No Health Problems Detected') }
- else
= @errors
- = render partial: 'failing_storages', object: @failing_storage_statuses
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index 3cee5841bbc..31d4b3da4f1 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -4,9 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 985928305a2..50f39f93283 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -3,9 +3,6 @@
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if params[:filter].blank? && @groups.empty?
= render 'shared/groups/empty_state'
- else
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 91f58ddcfcc..86a21e24ac9 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,9 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 27f53a8d1c6..61aae31be60 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -2,9 +2,6 @@
- page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id)
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
.top-area
= render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set
.nav-controls
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index f0d16936a51..deed774a4a5 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -4,9 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 42638b8528d..8933d9e31ff 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -4,9 +4,6 @@
- page_title "Starred Projects"
- header_title "Projects", dashboard_projects_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
%div{ class: container_class }
= render "projects/last_push"
= render 'dashboard/projects_head'
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index bbfa4cc7413..8b3974d97f8 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -2,9 +2,6 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if current_user.todos.any?
.top-area
%ul.nav-links.mobile-separator.nav.nav-tabs
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index 269a3721e06..12271ee5adb 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -5,7 +5,7 @@
.d-flex.justify-content-between.flex-wrap
- providers.each do |provider|
- has_icon = provider_has_icon?(provider)
- = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'btn d-flex align-items-center omniauth-btn text-left oauth-login', id: "oauth-login-#{provider}" do
+ = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'btn d-flex align-items-center omniauth-btn text-left oauth-login qa-saml-login-button', id: "oauth-login-#{provider}" do
- if has_icon
= provider_image_tag(provider)
%span
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 90ed20404c5..004a3528d4b 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -5,26 +5,26 @@
= devise_error_messages!
.form-group
= f.label :name, 'Full name', class: 'label-bold'
- = f.text_field :name, class: "form-control top", required: true, title: "This field is required."
+ = f.text_field :name, class: "form-control top qa-new-user-name", required: true, title: "This field is required."
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
+ = f.text_field :username, class: "form-control middle qa-new-user-username", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
.form-group
= f.label :email, class: 'label-bold'
- = f.email_field :email, class: "form-control middle", required: true, title: "Please provide a valid email address."
+ = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: "Please provide a valid email address."
.form-group
= f.label :email_confirmation, class: 'label-bold'
- = f.email_field :email_confirmation, class: "form-control middle", required: true, title: "Please retype the email address."
+ = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: "Please retype the email address."
.form-group.append-bottom-20#password-strength
= f.label :password, class: 'label-bold'
- = f.password_field :password, class: "form-control bottom", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters."
+ = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters."
%p.gl-field-hint.text-secondary Minimum length is #{@minimum_password_length} characters
- if Gitlab::CurrentSettings.current_application_settings.enforce_terms?
.form-group
- = check_box_tag :terms_opt_in, '1', false, required: true
+ = check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms'
= label_tag :terms_opt_in do
- terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank"
- accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link }
@@ -33,4 +33,4 @@
- if Gitlab::Recaptcha.enabled?
= recaptcha_tags
.submit-container
- = f.submit "Register", class: "btn-register btn"
+ = f.submit "Register", class: "btn-register btn qa-new-user-register-button"
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
index 776bbc36ec2..cac00f9c854 100644
--- a/app/views/doorkeeper/applications/show.html.haml
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -25,7 +25,7 @@
.input-group
%input.label.label-monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
.input-group-append
- = clipboard_button(target: '#application_id', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
+ = clipboard_button(target: '#secret', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
%tr
%td
= _('Callback URL')
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 1d8b9c5bc8f..a3eafc61d0a 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -2,9 +2,6 @@
- page_title _("Groups")
- header_title _("Groups"), dashboard_groups_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/groups_head'
- else
@@ -13,7 +10,7 @@
- if cookies[:explore_groups_landing_dismissed] != 'true'
.explore-groups.landing.content-block.js-explore-groups-landing.hide
- %button.dismiss-button{ type: 'button', 'aria-label' => _('Dismiss') }= icon('times')
+ %button.dismiss-button{ type: 'button', 'aria-label' => _('Dismiss') }= sprite_icon('close', size: 16)
.svg-container
= custom_icon('icon_explore_groups_splash')
.inner-content
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index 16be5791f83..452f390695c 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -2,9 +2,6 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index 16be5791f83..452f390695c 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -2,9 +2,6 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 16be5791f83..452f390695c 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -2,9 +2,6 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index fc17dd2d310..f3792c5e397 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -3,31 +3,31 @@
- expanded = Rails.env.test?
-%section.settings.gs-general.no-animate#js-general-settings{ class: ('expanded' if expanded) }
+%section.settings.gs-general.no-animate#js-general-settings{ class: ('expanded') }
.settings-header
- %h4
- = _('General')
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
+ = _('Naming, visibility')
%button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = _('Collapse')
%p
- = _('Update your group name, description, avatar, and other general settings.')
+ = _('Update your group name, description, avatar, and visibility.')
.settings-content
= render 'groups/settings/general'
%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
- = _('Permissions')
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
+ = _('Permissions, LFS, 2FA')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
- = _('Enable or disable certain group features and choose access levels.')
+ = _('Advanced permissions, Large File Storage and Two-Factor authentication settings.')
.settings-content
= render 'groups/settings/permissions'
-%section.settings.no-animate{ class: ('expanded' if expanded) }
+%section.settings.no-animate#js-badge-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
= s_('GroupSettings|Badges')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
@@ -39,8 +39,8 @@
%section.settings.gs-advanced.no-animate#js-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
- = _('Advanced')
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
+ = _('Path, transfer, remove')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 683129fdf6e..86c5f6a7aa3 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -27,7 +27,7 @@
.col-sm-10
= render 'shared/choose_group_avatar_button', f: f
- = render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group
+ = render 'shared/old_visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
= render 'create_chat_team', f: f if Gitlab.config.mattermost.enabled
diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml
index 3814d45929d..5d211d0e186 100644
--- a/app/views/groups/settings/_advanced.html.haml
+++ b/app/views/groups/settings/_advanced.html.haml
@@ -23,16 +23,6 @@
= f.submit 'Change group path', class: 'btn btn-warning'
-.sub-section
- %h4.danger-title Remove group
- = form_tag(@group, method: :delete) do
- %p
- Removing group will cause all child projects and resources to be removed.
- %br
- %strong Removed group can not be restored!
-
- = button_to 'Remove group', '#', class: 'btn btn-remove js-confirm-danger', data: { 'confirm-danger-message' => remove_group_message(@group) }
-
- if supports_nested_groups?
.sub-section
%h4.warning-title Transfer group
@@ -47,3 +37,13 @@
%li You will need to update your local repositories to point to the new location.
%li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
= f.submit 'Transfer group', class: 'btn btn-warning'
+
+.sub-section
+ %h4.danger-title= _('Remove group')
+ = form_tag(@group, method: :delete) do
+ %p
+ = _('Removing group will cause all child projects and resources to be removed.')
+ %br
+ %strong= _('Removed group can not be restored!')
+
+ = button_to _('Remove group'), '#', class: 'btn btn-remove js-confirm-danger', data: { 'confirm-danger-message' => remove_group_message(@group) }
diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml
index 0e225fe33a5..0424ece037d 100644
--- a/app/views/groups/settings/_general.html.haml
+++ b/app/views/groups/settings/_general.html.haml
@@ -1,39 +1,33 @@
-= form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f|
+= form_for @group, html: { multipart: true, class: 'gl-show-field-errors js-general-settings-form' }, authenticity_token: true do |f|
%input{ type: 'hidden', name: 'update_section', value: 'js-general-settings' }
= form_errors(@group)
%fieldset
.row
- .form-group.col-md-9
- = f.label :name, class: 'label-bold' do
- Group name
+ .form-group.col-md-5
+ = f.label :name, _('Group name'), class: 'label-bold'
= f.text_field :name, class: 'form-control'
- .form-group.col-md-3
- = f.label :id, class: 'label-bold' do
- Group ID
- = f.text_field :id, class: 'form-control', readonly: true
+ .form-group.col-md-7
+ = f.label :id, _('Group ID'), class: 'label-bold'
+ = f.text_field :id, class: 'form-control w-auto', readonly: true
- .form-group
- = f.label :description, class: 'label-bold' do
- Group description
- %span.light (optional)
- = f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
+ .row.prepend-top-8
+ .form-group.col-md-9.append-bottom-0
+ = f.label :description, _('Group description (optional)'), class: 'label-bold'
+ = f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
- .form-group.row
- .col-sm-12
- .avatar-container.s160
- = group_icon(@group, alt: '', class: 'avatar group-avatar s160')
- %p.light
- - if @group.avatar?
- You can change the group avatar here
- - else
- You can upload a group avatar here
- = render 'shared/choose_group_avatar_button', f: f
- - if @group.avatar?
- %hr
- = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-danger btn-inverted'
+ .form-group.prepend-top-default.append-bottom-20
+ .avatar-container.s90
+ = group_icon(@group, alt: '', class: 'avatar group-avatar s90')
+ = f.label :avatar, _('Group avatar'), class: 'label-bold d-block'
+ = render 'shared/choose_group_avatar_button', f: f
+ - if @group.avatar?
+ %hr
+ = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-danger btn-inverted'
- = f.submit 'Save group', class: 'btn btn-success'
+ = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
+
+ = f.submit _('Save changes'), class: 'btn btn-success mt-4 js-dirty-submit'
diff --git a/app/views/groups/settings/_lfs.html.haml b/app/views/groups/settings/_lfs.html.haml
new file mode 100644
index 00000000000..4674d561c12
--- /dev/null
+++ b/app/views/groups/settings/_lfs.html.haml
@@ -0,0 +1,15 @@
+- docs_link_url = help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: docs_link_url }
+
+%h5= _('Large File Storage')
+
+%p= s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
+
+.form-group.append-bottom-default
+ .form-check
+ = f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input'
+ = f.label :lfs_enabled, class: 'form-check-label' do
+ %span
+ = _('Allow projects within this group to use Git LFS')
+ %br/
+ %span.text-muted= _('This setting can be overridden in each project.')
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index 8dc88ec446c..6b0a6e7ed99 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -1,29 +1,24 @@
-= form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f|
+= form_for @group, html: { multipart: true, class: 'gl-show-field-errors js-general-permissions-form' }, authenticity_token: true do |f|
%input{ type: 'hidden', name: 'update_section', value: 'js-permissions-settings' }
= form_errors(@group)
%fieldset
- = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
+ %h5= _('Permissions')
+ .form-group
+ = render 'shared/allow_request_access', form: f
- .form-group.row
- .offset-sm-2.col-sm-10
- = render 'shared/allow_request_access', form: f
-
- .form-group.row
- %label.col-form-label.col-sm-2.pt-0
- = s_('GroupSettings|Share with group lock')
- .col-sm-10
- .form-check
- = f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
- = f.label :share_with_group_lock, class: 'form-check-label' do
- %strong
- - group_link = link_to @group.name, group_path(@group)
- = s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
- %br
- %span.descr= share_with_group_lock_help_text(@group)
-
- = render 'groups/group_admin_settings', f: f
+ .form-group.append-bottom-default
+ .form-check
+ = f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
+ = f.label :share_with_group_lock, class: 'form-check-label' do
+ %span
+ - group_link = link_to @group.name, group_path(@group)
+ = s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
+ %br
+ %span.descr.text-muted= share_with_group_lock_help_text(@group)
+ = render 'groups/settings/lfs', f: f
+ = render 'groups/settings/two_factor_auth', f: f
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
- = f.submit 'Save group', class: 'btn btn-success'
+ = f.submit _('Save changes'), class: 'btn btn-success prepend-top-default js-dirty-submit'
diff --git a/app/views/groups/settings/_two_factor_auth.html.haml b/app/views/groups/settings/_two_factor_auth.html.haml
new file mode 100644
index 00000000000..5d3f1cbb279
--- /dev/null
+++ b/app/views/groups/settings/_two_factor_auth.html.haml
@@ -0,0 +1,16 @@
+- docs_link_url = help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group')
+- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: docs_link_url }
+
+%h5= _('Two-factor authentication')
+
+%p= s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
+
+.form-group
+ .form-check
+ = f.check_box :require_two_factor_authentication, class: 'form-check-input'
+ = f.label :require_two_factor_authentication, class: 'form-check-label' do
+ %span= _('Require all users in this group to setup Two-factor authentication')
+.form-group
+ = f.label :two_factor_grace_period, _('Time before enforced'), class: 'label-bold'
+ = f.text_field :two_factor_grace_period, class: 'form-control form-control-sm w-auto'
+ .form-text.text-muted= _('Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication')
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 6a293daaf95..cc294f6a931 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -53,7 +53,7 @@
= _("Archived projects")
.nav-controls
- = render "shared/groups/dropdown"
+ = render "shared/groups/dropdown", options_hash: subgroups_sort_options_hash
.tab-content
#subgroups_and_projects.tab-pane
diff --git a/app/views/koding/index.html.haml b/app/views/koding/index.html.haml
deleted file mode 100644
index bb7f9ba7ae4..00000000000
--- a/app/views/koding/index.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.row-content-block.second-block.center
- %p
- = icon('circle', class: 'cgreen')
- Integration is active for
- = link_to koding_project_url, target: '_blank', rel: 'noopener noreferrer' do
- #{Gitlab::CurrentSettings.koding_url}
diff --git a/app/views/layouts/koding.html.haml b/app/views/layouts/koding.html.haml
deleted file mode 100644
index 45ccd38f687..00000000000
--- a/app/views/layouts/koding.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-- page_title _("Koding")
-- page_description _("Koding Dashboard")
-- header_title _("Koding"), koding_path
-
-= render template: "layouts/application"
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
index c35451827c8..f53bd2b5e4d 100644
--- a/app/views/layouts/nav/_breadcrumbs.html.haml
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -1,7 +1,6 @@
- container = @no_breadcrumb_container ? 'container-fluid' : container_class
- hide_top_links = @hide_top_links || false
-= yield :above_breadcrumbs_content
%nav.breadcrumbs{ role: "navigation", class: [container, @content_class] }
.breadcrumbs-container
- if defined?(@left_sidebar)
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 5e467c862ab..8f8b6b454d9 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -66,6 +66,7 @@
- if Gitlab::Sherlock.enabled? || can?(current_user, :read_instance_statistics)
%li.line-separator.d-none.d-sm-block
+ = render_if_exists 'dashboard/operations/nav_link'
- if can?(current_user, :read_instance_statistics)
= nav_link(controller: [:conversational_development_index, :cohorts]) do
= link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 48025f9bd20..3625224fbcd 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -309,7 +309,7 @@
%span
= _('General')
= nav_link(controller: :project_members) do
- = link_to project_project_members_path(@project), title: _('Members') do
+ = link_to project_project_members_path(@project), title: _('Members'), class: 'qa-link-members-settings' do
%span
= _('Members')
- if can_edit
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index ced6a2a0399..61ed951dea9 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -76,8 +76,6 @@
= render 'projects/buttons/download', project: @project, ref: @ref
.d-none.d-sm-inline-flex
= render 'projects/buttons/dropdown'
- .d-none.d-sm-inline-flex
- = render 'projects/buttons/koding'
.d-none.d-sm-inline-flex
= render 'shared/notifications/button', notification_setting: @notification_setting
diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml
index 5b092427496..2c8dd45670f 100644
--- a/app/views/projects/blob/_template_selectors.html.haml
+++ b/app/views/projects/blob/_template_selectors.html.haml
@@ -3,15 +3,15 @@
Template
.template-selector-dropdowns-wrap
.template-type-selector.js-template-type-selector-wrap.hidden
- = dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector', title: "Choose a template type" } )
+ = dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector qa-template-type-dropdown', title: "Choose a template type" } )
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
+ = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector qa-license-dropdown', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+ = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector qa-gitignore-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
+ = dropdown_tag("Apply a GitLab CI Yaml template", options: { toggle_class: 'js-gitlab-ci-yml-selector qa-gitlab-ci-yml-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls } } )
.dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
+ = dropdown_tag("Apply a Dockerfile template", options: { toggle_class: 'js-dockerfile-selector qa-dockerfile-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: dockerfile_names } } )
.template-selectors-undo-menu.hidden
%span.text-info Template applied
%button.btn.btn-sm.btn-info Undo
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 8b9c52f0802..45515fb492f 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -8,7 +8,7 @@
- if show_menu
.project-action-button.dropdown.inline
- %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...'), 'data-display' => 'static' }
+ %a.btn.dropdown-toggle.has-tooltip.qa-create-new-dropdown{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...'), 'data-display' => 'static' }
= icon('plus')
= icon("caret-down")
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
@@ -28,7 +28,7 @@
%li.dropdown-header= _('This repository')
- if can_push_code
- %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
+ %li.qa-new-file-option= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- unless @project.empty_repo?
%li= link_to _('New branch'), new_project_branch_path(@project)
%li= link_to _('New tag'), new_project_tag_path(@project)
diff --git a/app/views/projects/buttons/_koding.html.haml b/app/views/projects/buttons/_koding.html.haml
deleted file mode 100644
index e665ca61da8..00000000000
--- a/app/views/projects/buttons/_koding.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-- if koding_enabled? && current_user && @repository.koding_yml && @project.can_current_user_push_code?
- = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank', rel: 'noopener noreferrer' do
- _('Run in IDE (Koding)')
diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml
index 141314b4e4e..73cfea0ef92 100644
--- a/app/views/projects/clusters/_banner.html.haml
+++ b/app/views/projects/clusters/_banner.html.haml
@@ -9,7 +9,7 @@
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
- if show_cluster_security_warning?
- .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning{ data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } }
- %button.close.js-close{ type: "button" } &times;
+ .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning
+ %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } &times;
= s_("ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application.")
= link_to s_("More information"), help_page_path('user/project/clusters/index.md', anchor: 'security-implications')
diff --git a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
index 85d1002243b..73b11d509d3 100644
--- a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
@@ -1,6 +1,6 @@
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
-.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
- %button.close.js-close{ type: "button" } &times;
+.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert' }
+ %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } &times;
.gcp-signup-offer--content
.gcp-signup-offer--icon.append-right-8
= sprite_icon("information", size: 16)
diff --git a/app/views/projects/deploy_tokens/_form.html.haml b/app/views/projects/deploy_tokens/_form.html.haml
index 8b7535397bc..5412fcbc9d8 100644
--- a/app/views/projects/deploy_tokens/_form.html.haml
+++ b/app/views/projects/deploy_tokens/_form.html.haml
@@ -6,24 +6,24 @@
.form-group
= f.label :name, class: 'label-bold'
- = f.text_field :name, class: 'form-control', required: true
+ = f.text_field :name, class: 'form-control qa-deploy-token-name', required: true
.form-group
= f.label :expires_at, class: 'label-bold'
- = f.text_field :expires_at, class: 'datepicker form-control', value: f.object.expires_at
+ = f.text_field :expires_at, class: 'datepicker form-control qa-deploy-token-expires-at', value: f.object.expires_at
.form-group
= f.label :scopes, class: 'label-bold'
%fieldset.form-group.form-check
- = f.check_box :read_repository, class: 'form-check-input'
+ = f.check_box :read_repository, class: 'form-check-input qa-deploy-token-read-repository'
= label_tag ("deploy_token_read_repository"), 'read_repository', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read-only access to the repository')
- if container_registry_enabled?(project)
%fieldset.form-group.form-check
- = f.check_box :read_registry, class: 'form-check-input'
+ = f.check_box :read_registry, class: 'form-check-input qa-deploy-token-read-registry'
= label_tag ("deploy_token_read_registry"), 'read_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read-only access to the registry images')
.prepend-top-default
- = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success'
+ = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success qa-create-deploy-token'
diff --git a/app/views/projects/deploy_tokens/_index.html.haml b/app/views/projects/deploy_tokens/_index.html.haml
index 33faab0c510..4619522cfaf 100644
--- a/app/views/projects/deploy_tokens/_index.html.haml
+++ b/app/views/projects/deploy_tokens/_index.html.haml
@@ -1,6 +1,6 @@
- expanded = expand_deploy_tokens_section?(@new_deploy_token)
-%section.settings.no-animate#js-deploy-tokens{ class: ('expanded' if expanded) }
+%section.qa-deploy-tokens-settings.settings.no-animate#js-deploy-tokens{ class: ('expanded' if expanded) }
.settings-header
%h4= s_('DeployTokens|Deploy Tokens')
%button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
diff --git a/app/views/projects/deploy_tokens/_new_deploy_token.html.haml b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
index 5dd9ffba074..c805ee73acc 100644
--- a/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
+++ b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
@@ -1,18 +1,18 @@
-.created-deploy-token-container.info-well
+.qa-created-deploy-token-section.created-deploy-token-container.info-well
.well-segment
%h5.prepend-top-0
= s_('DeployTokens|Your New Deploy Token')
.form-group
.input-group
- = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus'
+ = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus qa-deploy-token-user'
.input-group-append
= clipboard_button(text: deploy_token.username, title: s_('DeployTokens|Copy username to clipboard'), placement: 'left')
%span.deploy-token-help-block.prepend-top-5.text-success= s_("DeployTokens|Use this username as a login.")
.form-group
.input-group
- = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus'
+ = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus qa-deploy-token'
.input-group-append
= clipboard_button(text: deploy_token.token, title: s_('DeployTokens|Copy deploy token to clipboard'), placement: 'left')
%span.deploy-token-help-block.prepend-top-5.text-danger= s_("DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again.")
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index a5f814b722d..02a088d338b 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -47,4 +47,6 @@
.js-build-options{ data: javascript_build_options }
-#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json), runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner') } }
+#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json),
+ runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'),
+ runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings') } }
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index ccb83148ded..dbb563f51ea 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -30,5 +30,3 @@
%span.js-details-content.hide
= link_to @pipeline.sha, project_commit_path(@project, @pipeline.sha), class: "commit-sha commit-hash-full"
= clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
-
- = render_if_exists "projects/pipelines/info_extension", pipeline: @pipeline
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 517fd249f6e..5e21442bb60 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -3,7 +3,7 @@
= form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f|
.form-group
= label_tag :user_ids, "Select members to invite", class: "label-bold"
- = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
+ = users_select_tag(:user_ids, multiple: true, class: "input-clamp qa-member-select-input", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
.form-group
= label_tag :access_level, "Choose a role permission", class: "label-bold"
.select-wrapper
@@ -17,5 +17,5 @@
= label_tag :expires_at, 'Access expiration date', class: 'label-bold'
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
- = f.submit "Add to project", class: "btn btn-success"
+ = f.submit "Add to project", class: "btn btn-success qa-add-member-button"
= link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 0c5a187f208..9682f8ac922 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -14,5 +14,5 @@
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search")
= render 'shared/members/sort_dropdown'
- %ul.content-list.members-list
+ %ul.content-list.members-list.qa-members-list
= render partial: 'shared/members/member', collection: members, as: :member
diff --git a/app/views/projects/services/prometheus/_configuration_banner.html.haml b/app/views/projects/services/prometheus/_configuration_banner.html.haml
index 898b55e4b39..dfcb1c5d240 100644
--- a/app/views/projects/services/prometheus/_configuration_banner.html.haml
+++ b/app/views/projects/services/prometheus/_configuration_banner.html.haml
@@ -7,7 +7,7 @@
- else
.container-fluid
.row
- - if service.prometheus_installed?
+ - if service.prometheus_available?
.col-sm-2
.svg-container
= image_tag 'illustrations/monitoring/getting_started.svg'
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index aba289c790f..283031b06da 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -8,8 +8,8 @@
= render partial: 'flash_messages', locals: { project: @project }
-- if @project.repository_exists? && !@project.empty_repo?
- - signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @project.default_branch)
+- if !@project.empty_repo? && can?(current_user, :download_code, @project)
+ - signatures_path = project_signatures_path(@project, @project.default_branch)
.js-signature-container{ data: { 'signatures-path': signatures_path } }
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 9d196075bf1..601e3f25852 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -82,7 +82,7 @@
- if can_collaborate
= succeed " " do
- = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default' do
+ = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
= _('Web IDE')
= render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/app/views/shared/_allow_request_access.html.haml b/app/views/shared/_allow_request_access.html.haml
index 92268e74b1e..a50f1877d08 100644
--- a/app/views/shared/_allow_request_access.html.haml
+++ b/app/views/shared/_allow_request_access.html.haml
@@ -1,6 +1,8 @@
+- label_class = local_assigns.fetch(:bold_label, false) ? 'font-weight-bold' : ''
+
.form-check
= form.check_box :request_access_enabled, class: 'form-check-input'
= form.label :request_access_enabled, class: 'form-check-label' do
- %strong Allow users to request access
+ %span{ class: label_class }= _('Allow users to request access')
%br
- %span.descr Allow users to request access if visibility is public or internal.
+ %span.text-muted= _('Allow users to request access if visibility is public or internal.')
diff --git a/app/views/shared/_old_visibility_level.html.haml b/app/views/shared/_old_visibility_level.html.haml
new file mode 100644
index 00000000000..fd576e4fbea
--- /dev/null
+++ b/app/views/shared/_old_visibility_level.html.haml
@@ -0,0 +1,6 @@
+.form-group.row
+ .col-sm-2.col-form-label
+ = _('Visibility level')
+ = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ .col-sm-10
+ = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_visibility_level, form_model: form_model, with_label: with_label
diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index ba37b37a3b1..2f42a877beb 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -1,17 +1,19 @@
- with_label = local_assigns.fetch(:with_label, true)
-.form-group.row.visibility-level-setting
+.form-group.visibility-level-setting
- if with_label
- = f.label :visibility_level, class: 'col-form-label col-sm-2 pt-0' do
- Visibility Level
- = link_to icon('question-circle'), help_page_path("public_access/public_access")
- %div{ :class => (with_label ? "col-sm-10" : "col-sm-12") }
- - if can_change_visibility_level
- = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
- - else
- %div
- %span.info
- = visibility_level_icon(visibility_level)
- %strong
- = visibility_level_label(visibility_level)
- .light= visibility_level_description(visibility_level, form_model)
+ = f.label :visibility_level, _('Visibility level'), class: 'label-bold append-bottom-0'
+ %p
+ = _('Who can see this group?')
+ - visibility_docs_path = help_page_path('public_access/public_access')
+ - docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: visibility_docs_path }
+ = s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
+ - if can_change_visibility_level
+ = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
+ - else
+ %div
+ %span.info
+ = visibility_level_icon(visibility_level)
+ %strong
+ = visibility_level_label(visibility_level)
+ .light= visibility_level_description(visibility_level, form_model)
diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml
index 5351c9ce6a4..df3308abe0d 100644
--- a/app/views/shared/empty_states/_wikis.html.haml
+++ b/app/views/shared/empty_states/_wikis.html.haml
@@ -5,7 +5,7 @@
- create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn btn-success', title: s_('WikiEmpty|Create your first page')
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do
- %h4
+ %h4.text-left
= s_('WikiEmpty|The wiki lets you write documentation for your project')
%p.text-left
= s_("WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on.")
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index e1da05d8f08..06eb3d03e31 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -8,6 +8,7 @@
- user = local_assigns[:user]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
- remote = false unless local_assigns[:remote] == true
+- skip_pagination = false unless local_assigns[:skip_pagination] == true
.js-projects-list-holder
- if any_projects?(projects)
@@ -25,6 +26,6 @@
= icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
%strong= pluralize(@private_forks_count, 'private fork')
%span &nbsp;you have no access to.
- = paginate_collection(projects, remote: remote)
+ = paginate_collection(projects, remote: remote) unless skip_pagination
- else
.nothing-here-block No projects found
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 1b66d3acd40..cf9c3055499 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -14,7 +14,7 @@
= render 'shared/form_elements/description', model: @snippet, project: @project, form: f
- = render 'shared/visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet
+ = render 'shared/old_visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet, with_label: false
.file-editor
.form-group.row
diff --git a/bin/storage_check b/bin/storage_check
deleted file mode 100755
index 5a818732bd1..00000000000
--- a/bin/storage_check
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'optparse'
-require 'net/http'
-require 'json'
-require 'socket'
-require 'logger'
-
-require_relative '../lib/gitlab/storage_check'
-
-Gitlab::StorageCheck::CLI.start!(ARGV)
diff --git a/changelogs/unreleased/22311-fix-duplicated-key-in-license-management-job.yml b/changelogs/unreleased/22311-fix-duplicated-key-in-license-management-job.yml
new file mode 100644
index 00000000000..ab64a1387d9
--- /dev/null
+++ b/changelogs/unreleased/22311-fix-duplicated-key-in-license-management-job.yml
@@ -0,0 +1,5 @@
+---
+title: "fix duplicated key in license management job auto devops gitlab ci template"
+merge_request: 22311
+author: Adam Lemanski
+type: fixed
diff --git a/changelogs/unreleased/48684-sort-projects-by-stars-in-groups.yml b/changelogs/unreleased/48684-sort-projects-by-stars-in-groups.yml
new file mode 100644
index 00000000000..01681adab24
--- /dev/null
+++ b/changelogs/unreleased/48684-sort-projects-by-stars-in-groups.yml
@@ -0,0 +1,4 @@
+---
+title: Add new sort option "most_stars" to "Group > Children" pages
+merge_request: 22121
+author: Rene Hennig
diff --git a/changelogs/unreleased/48889-message-for-were-merged-into.yml b/changelogs/unreleased/48889-message-for-were-merged-into.yml
new file mode 100644
index 00000000000..552b8826829
--- /dev/null
+++ b/changelogs/unreleased/48889-message-for-were-merged-into.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 'merged with' UI being displayed when merge request has no merge commit
+merge_request: 22022
+author:
+type: fixed
diff --git a/changelogs/unreleased/49417-improve-settings-pages-design-by-prioritizing-content-group-settings.yml b/changelogs/unreleased/49417-improve-settings-pages-design-by-prioritizing-content-group-settings.yml
new file mode 100644
index 00000000000..8ded24a1cd0
--- /dev/null
+++ b/changelogs/unreleased/49417-improve-settings-pages-design-by-prioritizing-content-group-settings.yml
@@ -0,0 +1,5 @@
+---
+title: Update group settings/edit page to new design
+merge_request: 21115
+author:
+type: other
diff --git a/changelogs/unreleased/50185-fix-broken-file-name-navigation.yml b/changelogs/unreleased/50185-fix-broken-file-name-navigation.yml
new file mode 100644
index 00000000000..d1b341af457
--- /dev/null
+++ b/changelogs/unreleased/50185-fix-broken-file-name-navigation.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken file name navigation on MRs
+merge_request: 22109
+author:
+type: fixed
diff --git a/changelogs/unreleased/51958-fix-mr-discussion-loading.yml b/changelogs/unreleased/51958-fix-mr-discussion-loading.yml
deleted file mode 100644
index f80ee51291d..00000000000
--- a/changelogs/unreleased/51958-fix-mr-discussion-loading.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix loading issue on some merge request discussion
-merge_request: 21982
-author:
-type: fixed
diff --git a/changelogs/unreleased/51972-prometheus-not-showing-as-installed-even-though-it-is.yml b/changelogs/unreleased/51972-prometheus-not-showing-as-installed-even-though-it-is.yml
new file mode 100644
index 00000000000..73b035fca00
--- /dev/null
+++ b/changelogs/unreleased/51972-prometheus-not-showing-as-installed-even-though-it-is.yml
@@ -0,0 +1,5 @@
+---
+title: Show available clusters when installed or updated
+merge_request: 22356
+author:
+type: fixed
diff --git a/changelogs/unreleased/52147-loading-state.yml b/changelogs/unreleased/52147-loading-state.yml
new file mode 100644
index 00000000000..5ae844073f7
--- /dev/null
+++ b/changelogs/unreleased/52147-loading-state.yml
@@ -0,0 +1,5 @@
+---
+title: Removes extra border from test reports in the merge request widget
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/52361-fix-file-tree-mobile.yml b/changelogs/unreleased/52361-fix-file-tree-mobile.yml
new file mode 100644
index 00000000000..fe978eeca2d
--- /dev/null
+++ b/changelogs/unreleased/52361-fix-file-tree-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Improve MR file tree in smaller screens
+merge_request: 22273
+author:
+type: fixed
diff --git a/changelogs/unreleased/52421-show-canary-no-canary-in-the-performance-bar.yml b/changelogs/unreleased/52421-show-canary-no-canary-in-the-performance-bar.yml
new file mode 100644
index 00000000000..20e32a2e987
--- /dev/null
+++ b/changelogs/unreleased/52421-show-canary-no-canary-in-the-performance-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Show canary status in the performance bar
+merge_request: 22222
+author:
+type: changed
diff --git a/changelogs/unreleased/52472-pipeline-endpoint-json.yml b/changelogs/unreleased/52472-pipeline-endpoint-json.yml
new file mode 100644
index 00000000000..feff195beb8
--- /dev/null
+++ b/changelogs/unreleased/52472-pipeline-endpoint-json.yml
@@ -0,0 +1,5 @@
+---
+title: Fix caching issue with pipelines URL
+merge_request: 22293
+author:
+type: fixed
diff --git a/changelogs/unreleased/52477-add-iid-headers-to-emails.yml b/changelogs/unreleased/52477-add-iid-headers-to-emails.yml
new file mode 100644
index 00000000000..c17b66c5f54
--- /dev/null
+++ b/changelogs/unreleased/52477-add-iid-headers-to-emails.yml
@@ -0,0 +1,5 @@
+---
+title: Add IID headers to E-Mail notifications
+merge_request: 22263
+author:
+type: changed
diff --git a/changelogs/unreleased/52519-runners-link.yml b/changelogs/unreleased/52519-runners-link.yml
new file mode 100644
index 00000000000..5d904a8b340
--- /dev/null
+++ b/changelogs/unreleased/52519-runners-link.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes stuck block URL linking to documentation instead of settings page
+merge_request: 22286
+author:
+type: fixed
diff --git a/changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml b/changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml
new file mode 100644
index 00000000000..9abad3d2cd8
--- /dev/null
+++ b/changelogs/unreleased/52532-unable-to-toggle-issuable-sidebar-out-of-collapsed-state.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Issue and Merge Request sidebar to be toggled from collapsed state
+merge_request: 22353
+author:
+type: fixed
diff --git a/changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml b/changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml
new file mode 100644
index 00000000000..bddc1e16fab
--- /dev/null
+++ b/changelogs/unreleased/52564-personal-projects-pagination-in-profile-overview-tab-is-broken.yml
@@ -0,0 +1,5 @@
+---
+title: Hide pagination for personal projects on profile overview tab
+merge_request: 22321
+author:
+type: other
diff --git a/changelogs/unreleased/52570-erased-block.yml b/changelogs/unreleased/52570-erased-block.yml
new file mode 100644
index 00000000000..6ec295bf81b
--- /dev/null
+++ b/changelogs/unreleased/52570-erased-block.yml
@@ -0,0 +1,5 @@
+---
+title: Fix erased block not being rendered when job was erased
+merge_request: 22294
+author:
+type: fixed
diff --git a/changelogs/unreleased/52608-sidebar.yml b/changelogs/unreleased/52608-sidebar.yml
new file mode 100644
index 00000000000..9eca30f7b95
--- /dev/null
+++ b/changelogs/unreleased/52608-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Hides sidebar for job page in mobile
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/52614-update-job-started-check.yml b/changelogs/unreleased/52614-update-job-started-check.yml
new file mode 100644
index 00000000000..60ea237dbf3
--- /dev/null
+++ b/changelogs/unreleased/52614-update-job-started-check.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes triggered/created labeled in job header
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml b/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml
new file mode 100644
index 00000000000..fdbde709e77
--- /dev/null
+++ b/changelogs/unreleased/52618-incorrect-stage-being-shown-in-side-bar-of-job-view-api.yml
@@ -0,0 +1,5 @@
+---
+title: Load correct stage in the stages dropdown
+merge_request: 22317
+author:
+type: fixed
diff --git a/changelogs/unreleased/52669-fixes-quick-actions-preview.yml b/changelogs/unreleased/52669-fixes-quick-actions-preview.yml
new file mode 100644
index 00000000000..51b1425d04d
--- /dev/null
+++ b/changelogs/unreleased/52669-fixes-quick-actions-preview.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes close/reopen quick actions preview for issues and merge_requests
+merge_request: 22343
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/52686-project-slug-does-not-auto-populate-in-ie11.yml b/changelogs/unreleased/52686-project-slug-does-not-auto-populate-in-ie11.yml
new file mode 100644
index 00000000000..5a30317babf
--- /dev/null
+++ b/changelogs/unreleased/52686-project-slug-does-not-auto-populate-in-ie11.yml
@@ -0,0 +1,5 @@
+---
+title: Use literal instead of constructor for creating regex
+merge_request: 22367
+author:
+type: other
diff --git a/changelogs/unreleased/bvl-merge-base-multiple-revisions.yml b/changelogs/unreleased/bvl-merge-base-multiple-revisions.yml
new file mode 100644
index 00000000000..4075e35fce9
--- /dev/null
+++ b/changelogs/unreleased/bvl-merge-base-multiple-revisions.yml
@@ -0,0 +1,5 @@
+---
+title: Allow finding the common ancestor for multiple revisions through the API
+merge_request: 22295
+author:
+type: changed
diff --git a/changelogs/unreleased/da-fix-does-not-import-projects-over-ssh.yml b/changelogs/unreleased/da-fix-does-not-import-projects-over-ssh.yml
new file mode 100644
index 00000000000..5867b1f0981
--- /dev/null
+++ b/changelogs/unreleased/da-fix-does-not-import-projects-over-ssh.yml
@@ -0,0 +1,5 @@
+---
+title: Does not allow a SSH URI when importing new projects
+merge_request: 22309
+author:
+type: fixed
diff --git a/changelogs/unreleased/diff-stats-perf-bar.yml b/changelogs/unreleased/diff-stats-perf-bar.yml
new file mode 100644
index 00000000000..52d70d4537f
--- /dev/null
+++ b/changelogs/unreleased/diff-stats-perf-bar.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed diff stats not showing when performance bar is enabled
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/enable-frozen-string-lib-gitlab.yml b/changelogs/unreleased/enable-frozen-string-lib-gitlab.yml
new file mode 100644
index 00000000000..4a216c46d38
--- /dev/null
+++ b/changelogs/unreleased/enable-frozen-string-lib-gitlab.yml
@@ -0,0 +1,5 @@
+---
+title: Enable some frozen string in lib/gitlab
+merge_request:
+author: gfyoung
+type: performance
diff --git a/changelogs/unreleased/even-more-frozen-string-lib.yml b/changelogs/unreleased/even-more-frozen-string-lib.yml
new file mode 100644
index 00000000000..3f5fd7710aa
--- /dev/null
+++ b/changelogs/unreleased/even-more-frozen-string-lib.yml
@@ -0,0 +1,5 @@
+---
+title: Enable even more frozen string in lib/**/*.rb
+merge_request:
+author: gfyoung
+type: performance
diff --git a/changelogs/unreleased/feature-improved-branch-filter-sorting.yml b/changelogs/unreleased/feature-improved-branch-filter-sorting.yml
new file mode 100644
index 00000000000..539c297e0dd
--- /dev/null
+++ b/changelogs/unreleased/feature-improved-branch-filter-sorting.yml
@@ -0,0 +1,6 @@
+---
+title: Improving branch filter sorting by listing exact matches first and added support
+ for begins_with (^) and ends_with ($) matching.
+merge_request: 22166
+author: Jason Rutherford
+type: changed
diff --git a/changelogs/unreleased/fl-update-svgs.yml b/changelogs/unreleased/fl-update-svgs.yml
new file mode 100644
index 00000000000..e6e76617df1
--- /dev/null
+++ b/changelogs/unreleased/fl-update-svgs.yml
@@ -0,0 +1,5 @@
+---
+title: Updates svg dependency
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml b/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml
new file mode 100644
index 00000000000..7205c138777
--- /dev/null
+++ b/changelogs/unreleased/gt-update-application-copy-secret-to-clipboard-data.yml
@@ -0,0 +1,5 @@
+---
+title: Update copy to clipboard button data for application secret
+merge_request: 22268
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/gt-update-wiki-empty-state.yml b/changelogs/unreleased/gt-update-wiki-empty-state.yml
new file mode 100644
index 00000000000..76f923ae814
--- /dev/null
+++ b/changelogs/unreleased/gt-update-wiki-empty-state.yml
@@ -0,0 +1,5 @@
+---
+title: Update wiki empty state
+merge_requrst: 22218
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/ide-file-templates-clear.yml b/changelogs/unreleased/ide-file-templates-clear.yml
new file mode 100644
index 00000000000..7878f2231a7
--- /dev/null
+++ b/changelogs/unreleased/ide-file-templates-clear.yml
@@ -0,0 +1,5 @@
+---
+title: Clear fetched file templates when changing template type in Web IDE
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/replace-i-to-icons-in-vue-components.yml b/changelogs/unreleased/replace-i-to-icons-in-vue-components.yml
new file mode 100644
index 00000000000..6de57b04338
--- /dev/null
+++ b/changelogs/unreleased/replace-i-to-icons-in-vue-components.yml
@@ -0,0 +1,5 @@
+---
+title: Replace i to icons in vue components
+merge_request: 20748
+author: George Tsiolis
+type: changed
diff --git a/changelogs/unreleased/sh-fix-commit-signatures-error.yml b/changelogs/unreleased/sh-fix-commit-signatures-error.yml
new file mode 100644
index 00000000000..e2ea0e5857e
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-commit-signatures-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix commit signature error when project is disabled
+merge_request: 22344
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-remove-koding.yml b/changelogs/unreleased/sh-remove-koding.yml
new file mode 100644
index 00000000000..2c4e8c76a61
--- /dev/null
+++ b/changelogs/unreleased/sh-remove-koding.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Koding integration and documentation
+merge_request: 22334
+author:
+type: removed
diff --git a/changelogs/unreleased/test-usage-ping-in-timeout-case.yml b/changelogs/unreleased/test-usage-ping-in-timeout-case.yml
new file mode 100644
index 00000000000..daee98765ac
--- /dev/null
+++ b/changelogs/unreleased/test-usage-ping-in-timeout-case.yml
@@ -0,0 +1,5 @@
+---
+title: Fix auto-corrected upload URLs in webhooks
+merge_request: 22361
+author:
+type: fixed
diff --git a/changelogs/unreleased/zj-circuit-breaker-removal.yml b/changelogs/unreleased/zj-circuit-breaker-removal.yml
new file mode 100644
index 00000000000..f753cec993f
--- /dev/null
+++ b/changelogs/unreleased/zj-circuit-breaker-removal.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Git circuit breaker
+merge_request: 22212
+author:
+type: removed
diff --git a/changelogs/unreleased/zj-remove-linguist.yml b/changelogs/unreleased/zj-remove-linguist.yml
new file mode 100644
index 00000000000..5719512c4cc
--- /dev/null
+++ b/changelogs/unreleased/zj-remove-linguist.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Linguist gem, reducing Rails memory usage by 128MB per process
+merge_request: 21008
+author:
+type: changed
diff --git a/config/routes.rb b/config/routes.rb
index 1242bbbf932..c081ca9672a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -56,7 +56,6 @@ Rails.application.routes.draw do
# '/-/health' implemented by BasicHealthMiddleware
get 'liveness' => 'health#liveness'
get 'readiness' => 'health#readiness'
- post 'storage_check' => 'health#storage_check'
resources :metrics, only: [:index]
mount Peek::Railtie => '/peek', as: 'peek_routes'
@@ -82,9 +81,6 @@ Rails.application.routes.draw do
draw :instance_statistics
end
- # Koding route
- get 'koding' => 'koding#index'
-
draw :api
draw :sidekiq
draw :help
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 7cdaa2daa14..fb29c4748c1 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -69,9 +69,7 @@ namespace :admin do
end
resource :logs, only: [:show]
- resource :health_check, controller: 'health_check', only: [:show] do
- post :reset_storage_health
- end
+ resource :health_check, controller: 'health_check', only: [:show]
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :system_info, controller: 'system_info', only: [:show]
resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ }
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index 020e9a00d87..e06cce3e97a 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -95,7 +95,7 @@ end
before_fork do |server, worker|
# the following is highly recommended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
- defined?(ActiveRecord::Base) and
+ defined?(ActiveRecord::Base) &&
ActiveRecord::Base.connection.disconnect!
# The following is only recommended for memory/DB-constrained
@@ -133,7 +133,7 @@ after_fork do |server, worker|
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
# the following is *required* for Rails + "preload_app true",
- defined?(ActiveRecord::Base) and
+ defined?(ActiveRecord::Base) &&
ActiveRecord::Base.establish_connection
# reset prometheus client, this will cause any opened metrics files to be closed
diff --git a/config/unicorn.rb.example.development b/config/unicorn.rb.example.development
index 5712549a66d..f31df66015a 100644
--- a/config/unicorn.rb.example.development
+++ b/config/unicorn.rb.example.development
@@ -7,7 +7,7 @@ check_client_connection false
before_fork do |server, worker|
# the following is highly recommended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
- defined?(ActiveRecord::Base) and
+ defined?(ActiveRecord::Base) &&
ActiveRecord::Base.connection.disconnect!
if /darwin/ =~ RUBY_PLATFORM
@@ -27,6 +27,6 @@ after_fork do |server, worker|
require 'rbtrace' if ENV['ENABLE_RBTRACE']
# the following is *required* for Rails + "preload_app true",
- defined?(ActiveRecord::Base) and
+ defined?(ActiveRecord::Base) &&
ActiveRecord::Base.establish_connection
end
diff --git a/db/post_migrate/20181008200441_remove_circuit_breaker.rb b/db/post_migrate/20181008200441_remove_circuit_breaker.rb
new file mode 100644
index 00000000000..838addb7286
--- /dev/null
+++ b/db/post_migrate/20181008200441_remove_circuit_breaker.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class RemoveCircuitBreaker < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ CIRCUIT_BREAKER_COLUMS_WITH_DEFAULT = {
+ circuitbreaker_failure_count_threshold: 3,
+ circuitbreaker_failure_reset_time: 1800,
+ circuitbreaker_storage_timeout: 15,
+ circuitbreaker_access_retries: 3,
+ circuitbreaker_check_interval: 1
+ }.freeze
+
+ def up
+ CIRCUIT_BREAKER_COLUMS_WITH_DEFAULT.keys.each do |column|
+ remove_column(:application_settings, column) if column_exists?(:application_settings, column)
+ end
+ end
+
+ def down
+ CIRCUIT_BREAKER_COLUMS_WITH_DEFAULT.each do |column, default|
+ add_column_with_default(:application_settings, column, :integer, default: default) unless column_exists?(:application_settings, column)
+ end
+ end
+end
diff --git a/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb b/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb
new file mode 100644
index 00000000000..938a32e4e98
--- /dev/null
+++ b/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RemoveKodingFromApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ remove_column :application_settings, :koding_enabled
+ remove_column :application_settings, :koding_url
+ end
+
+ def down
+ add_column :application_settings, :koding_enabled, :boolean # rubocop:disable Migration/SaferBooleanColumn
+ add_column :application_settings, :koding_url, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d47156c6da4..3f3bec0ce04 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20181008145359) do
+ActiveRecord::Schema.define(version: 20181013005024) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -104,8 +104,6 @@ ActiveRecord::Schema.define(version: 20181008145359) do
t.boolean "domain_blacklist_enabled", default: false
t.text "domain_blacklist"
t.boolean "usage_ping_enabled", default: true, null: false
- t.boolean "koding_enabled"
- t.string "koding_url"
t.text "sign_in_text_html"
t.text "help_page_text_html"
t.text "shared_runners_text_html"
@@ -139,10 +137,6 @@ ActiveRecord::Schema.define(version: 20181008145359) do
t.boolean "hashed_storage_enabled", default: false, null: false
t.boolean "project_export_enabled", default: true, null: false
t.boolean "auto_devops_enabled", default: true, null: false
- t.integer "circuitbreaker_failure_count_threshold", default: 3
- t.integer "circuitbreaker_failure_reset_time", default: 1800
- t.integer "circuitbreaker_storage_timeout", default: 15
- t.integer "circuitbreaker_access_retries", default: 3
t.boolean "throttle_unauthenticated_enabled", default: false, null: false
t.integer "throttle_unauthenticated_requests_per_period", default: 3600, null: false
t.integer "throttle_unauthenticated_period_in_seconds", default: 3600, null: false
@@ -152,7 +146,6 @@ ActiveRecord::Schema.define(version: 20181008145359) do
t.boolean "throttle_authenticated_web_enabled", default: false, null: false
t.integer "throttle_authenticated_web_requests_per_period", default: 7200, null: false
t.integer "throttle_authenticated_web_period_in_seconds", default: 3600, null: false
- t.integer "circuitbreaker_check_interval", default: 1, null: false
t.boolean "password_authentication_enabled_for_web"
t.boolean "password_authentication_enabled_for_git", default: true
t.integer "gitaly_timeout_default", default: 55, null: false
diff --git a/doc/README.md b/doc/README.md
index 7548240bfef..03371226041 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -33,7 +33,7 @@ provides solutions for all the stages of the DevOps lifecycle:
[plan](#plan), [create](#create), [verify](#verify), [package](#package),
[release](#release), [configure](#configure), [monitor](#monitor).
-![DevOps Lifecycle](img/devops_lifecycle.png)
+<img class="image-noshadow" src="img/devops_lifecycle.png" alt="DevOps Lifecycle">
### Plan
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index b5e2b5448f7..e1b2a0a24eb 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -25,15 +25,13 @@ gitaly['prometheus_listen_addr'] = 'localhost:9236'
```
To change a Gitaly setting in installations from source you can edit
-`/home/git/gitaly/config.toml`.
+`/home/git/gitaly/config.toml`. Changes will be applied when you run
+`service gitlab restart`.
```toml
prometheus_listen_addr = "localhost:9236"
```
-Changes to `/home/git/gitaly/config.toml` are applied when you run `service
-gitlab restart`.
-
## Client-side GRPC logs
Gitaly uses the [gRPC](https://grpc.io/) RPC framework. The Ruby gRPC
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 95e2caf0cad..040c9ecae55 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -3,6 +3,11 @@
You can view information and options set for each of the mounted NFS file
systems by running `nfsstat -m` and `cat /etc/fstab`.
+NOTE: **Note:** Filesystem performance has a big impact on overall GitLab
+performance, especially for actions that read or write to Git repositories. See
+[Filesystem Performance Benchmarking](../operations/filesystem_benchmarking.md)
+for steps to test filesystem performance.
+
## NFS Server features
### Required features
@@ -87,7 +92,7 @@ Mount `/gitlab-nfs` then use the following Omnibus
configuration to move each data location to a subdirectory:
```ruby
-git_data_dirs({"default" => "/gitlab-nfs/gitlab-data/git-data"})
+git_data_dirs({"default" => { "path" => "/gitlab-nfs/gitlab-data/git-data"} })
user['home'] = '/gitlab-nfs/gitlab-data/home'
gitlab_rails['uploads_directory'] = '/gitlab-nfs/gitlab-data/uploads'
gitlab_rails['shared_path'] = '/gitlab-nfs/gitlab-data/shared'
@@ -133,7 +138,7 @@ following are the 5 locations need to be shared:
| Location | Description | Default configuration |
| -------- | ----------- | --------------------- |
-| `/var/opt/gitlab/git-data` | Git repository data. This will account for a large portion of your data | `git_data_dirs({"default" => "/var/opt/gitlab/git-data"})`
+| `/var/opt/gitlab/git-data` | Git repository data. This will account for a large portion of your data | `git_data_dirs({"default" => { "path" => "/var/opt/gitlab/git-data"} })`
| `/var/opt/gitlab/.ssh` | SSH `authorized_keys` file and keys used to import repositories from some other Git services | `user['home'] = '/var/opt/gitlab/'`
| `/var/opt/gitlab/gitlab-rails/uploads` | User uploaded attachments | `gitlab_rails['uploads_directory'] = '/var/opt/gitlab/gitlab-rails/uploads'`
| `/var/opt/gitlab/gitlab-rails/shared` | Build artifacts, GitLab Pages, LFS objects, temp files, etc. If you're using LFS this may also account for a large portion of your data | `gitlab_rails['shared_path'] = '/var/opt/gitlab/gitlab-rails/shared'`
diff --git a/doc/administration/img/circuitbreaker_config.png b/doc/administration/img/circuitbreaker_config.png
deleted file mode 100644
index 20233276055..00000000000
--- a/doc/administration/img/circuitbreaker_config.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/img/failing_storage.png b/doc/administration/img/failing_storage.png
deleted file mode 100644
index 652d7dcb5d7..00000000000
--- a/doc/administration/img/failing_storage.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/index.md b/doc/administration/index.md
index d713247983b..8e6fa9563c7 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -118,10 +118,9 @@ created in snippets, wikis, and repos.
## Continuous Integration settings
- [Enable/disable GitLab CI/CD](../ci/enable_or_disable_ci.md#site-wide-admin-setting): Enable or disable GitLab CI/CD for your instance.
-- [GitLab CI/CD admin settings](../user/admin_area/settings/continuous_integration.md): Define max artifacts size and expiration time.
+- [GitLab CI/CD admin settings](../user/admin_area/settings/continuous_integration.md): Enable or disable Auto DevOps site-wide and define the artifacts' max size and expiration time.
- [Job artifacts](job_artifacts.md): Enable, disable, and configure job artifacts (a set of files and directories which are outputted by a job when it completes successfully).
- [Job traces](job_traces.md): Information about the job traces (logs).
-- [Artifacts size and expiration](../user/admin_area/settings/continuous_integration.md#maximum-artifacts-size): Define maximum artifacts limits and expiration date.
- [Register Shared and specific Runners](../ci/runners/README.md#registering-a-shared-runner): Learn how to register and configure Shared and specific Runners to your own instance.
- [Shared Runners pipelines quota](../user/admin_area/settings/continuous_integration.md#shared-runners-pipeline-minutes-quota): Limit the usage of pipeline minutes for Shared Runners.
- [Enable/disable Auto DevOps](../topics/autodevops/index.md#enabling-auto-devops): Enable or disable Auto DevOps for your instance.
diff --git a/doc/administration/integration/koding.md b/doc/administration/integration/koding.md
deleted file mode 100644
index def0add0061..00000000000
--- a/doc/administration/integration/koding.md
+++ /dev/null
@@ -1,246 +0,0 @@
-# Koding & GitLab
-
-> **Notes:**
-> - **As of GitLab 10.0, the Koding integration is deprecated and will be removed
-> in a future version. The option to configure it is removed from GitLab's admin
-> area.**
-> - [Introduced][ce-5909] in GitLab 8.11.
-
-This document will guide you through installing and configuring Koding with
-GitLab.
-
-First of all, to be able to use Koding and GitLab together you will need public
-access to your server. This allows you to use single sign-on from GitLab to
-Koding and using vms from cloud providers like AWS. Koding has a registry for
-VMs, called Kontrol and it runs on the same server as Koding itself, VMs from
-cloud providers register themselves to Kontrol via the agent that we put into
-provisioned VMs. This agent is called Klient and it provides Koding to access
-and manage the target machine.
-
-Kontrol and Klient are based on another technology called
-[Kite](https://github.com/koding/kite), that we have written at Koding. Which is a
-microservice framework that allows you to develop microservices easily.
-
-## Requirements
-
-### Hardware
-
-Minimum requirements are;
-
- - 2 cores CPU
- - 3G RAM
- - 10G Storage
-
-If you plan to use AWS to install Koding it is recommended that you use at
-least a `c3.xlarge` instance.
-
-### Software
-
- - [Git](https://git-scm.com)
- - [Docker](https://www.docker.com)
- - [docker-compose](https://www.docker.com/products/docker-compose)
-
-Koding can run on most of the UNIX based operating systems, since it's shipped
-as containerized with Docker support, it can work on any operating system that
-supports Docker.
-
-Required services are:
-
-- **PostgreSQL** - Kontrol and Service DB provider
-- **MongoDB** - Main DB provider the application
-- **Redis** - In memory DB used by both application and services
-- **RabbitMQ** - Message Queue for both application and services
-
-which are also provided as a Docker container by Koding.
-
-
-## Getting Started with Development Versions
-
-
-### Koding
-
-You can run `docker-compose` environment for developing koding by
-executing commands in the following snippet.
-
-```bash
-git clone https://github.com/koding/koding.git
-cd koding
-docker-compose -f docker-compose-init.yml run init
-docker-compose up
-```
-
-This should start koding on `localhost:8090`.
-
-By default there is no team exists in Koding DB. You'll need to create a team
-called `gitlab` which is the default team name for GitLab integration in the
-configuration. To make things in order it's recommended to create the `gitlab`
-team first thing after setting up Koding.
-
-
-### GitLab
-
-To install GitLab to your environment for development purposes it's recommended
-to use GitLab Development Kit which you can get it from
-[here](https://gitlab.com/gitlab-org/gitlab-development-kit).
-
-After all those steps, gitlab should be running on `localhost:3000`
-
-
-## Integration
-
-Integration includes following components;
-
- - Single Sign On with OAuth from GitLab to Koding
- - System Hook integration for handling GitLab events on Koding
- (`project_created`, `user_joined` etc.)
- - Service endpoints for importing/executing stacks from GitLab to Koding
- (`Run/Try on IDE (Koding)` buttons on GitLab Projects, Issues, MRs)
-
-As it's pointed out before, you will need public access to this machine that
-you've installed Koding and GitLab on. Better to use a domain but a static IP
-is also fine.
-
-For IP based installation you can use [nip.io](https://nip.io) service which is
-free and provides DNS resolution to IP based requests like following;
-
- - 127.0.0.1.nip.io -> resolves to 127.0.0.1
- - foo.bar.baz.127.0.0.1.nip.io -> resolves to 127.0.0.1
- - and so on...
-
-As Koding needs subdomains for team names; `foo.127.0.0.1.nip.io` requests for
-a running koding instance on `127.0.0.1` server will be handled as `foo` team
-requests.
-
-
-### GitLab Side
-
-You need to enable Koding integration from Settings under Admin Area. To do
-that login with an Admin account and do followings;
-
-- open [http://127.0.0.1:3000/admin/application_settings](http://127.0.0.1:3000/admin/application_settings)
-- scroll to bottom of the page until Koding section
-- check `Enable Koding` checkbox
-- provide GitLab team page for running Koding instance as `Koding URL`*
- * For `Koding URL` you need to provide the gitlab integration enabled team on
-your Koding installation. Team called `gitlab` has integration on Koding out
-of the box, so if you didn't change anything your team on Koding should be
-`gitlab`.
-
-So, if your Koding is running on `http://1.2.3.4.nip.io:8090` your URL needs
-to be `http://gitlab.1.2.3.4.nip.io:8090`. You need to provide the same host
-with your Koding installation here.
-
-
-#### Registering Koding for OAuth integration
-
-We need `Application ID` and `Secret` to enable login to Koding via GitLab
-feature and to do that you need to register running Koding as a new application
-to your running GitLab application. Follow
-[these](http://docs.gitlab.com/ce/integration/oauth_provider.html) steps to
-enable this integration.
-
-Redirect URI should be `http://gitlab.127.0.0.1:8090/-/oauth/gitlab/callback`
-which again you need to _replace `127.0.0.1` with your instance public IP._
-
-Take a copy of `Application ID` and `Secret` that is generated by the GitLab
-application, we will need those on _Koding Part_ of this guide.
-
-
-#### Registering system hooks to Koding (optional)
-
-Koding can take actions based on the events generated by GitLab application.
-This feature is still in progress and only following events are processed by
-Koding at the moment;
-
- - user_create
- - user_destroy
-
-All system events are handled but not implemented on Koding side.
-
-To enable this feature you need to provide a `URL` and a `Secret Token` to your
-GitLab application. Open your admin area on your GitLab app from
-[http://127.0.0.1:3000/admin/hooks](http://127.0.0.1:3000/admin/hooks)
-and provide `URL` as `http://gitlab.127.0.0.1:8090/-/api/gitlab` which is the
-endpoint to handle GitLab events on Koding side. Provide a `Secret Token` and
-keep a copy of it, we will need it on _Koding Part_ of this guide.
-
-_(replace `127.0.0.1` with your instance public IP)_
-
-
-### Koding Part
-
-If you followed the steps in GitLab part we should have followings to enable
-Koding part integrations;
-
- - `Application ID` and `Secret` for OAuth integration
- - `Secret Token` for system hook integration
- - Public address of running GitLab instance
-
-
-#### Start Koding with GitLab URL
-
-Now we need to configure Koding with all this information to get things ready.
-If it's already running please stop koding first.
-
-##### From command-line
-
-Replace followings with the ones you got from GitLab part of this guide;
-
-```bash
-cd koding
-docker-compose run \
- --service-ports backend \
- /opt/koding/scripts/bootstrap-container build \
- --host=**YOUR_IP**.nip.io \
- --gitlabHost=**GITLAB_IP** \
- --gitlabPort=**GITLAB_PORT** \
- --gitlabToken=**SECRET_TOKEN** \
- --gitlabAppId=**APPLICATION_ID** \
- --gitlabAppSecret=**SECRET**
-```
-
-##### By updating configuration
-
-Alternatively you can update `gitlab` section on
-`config/credentials.default.coffee` like following;
-
-```
-gitlab =
- host: '**GITLAB_IP**'
- port: '**GITLAB_PORT**'
- applicationId: '**APPLICATION_ID**'
- applicationSecret: '**SECRET**'
- team: 'gitlab'
- redirectUri: ''
- systemHookToken: '**SECRET_TOKEN**'
- hooksEnabled: yes
-```
-
-and start by only providing the `host`;
-
-```bash
-cd koding
-docker-compose run \
- --service-ports backend \
- /opt/koding/scripts/bootstrap-container build \
- --host=**YOUR_IP**.nip.io \
-```
-
-#### Enable Single Sign On
-
-Once you restarted your Koding and logged in with your username and password
-you need to activate oauth authentication for your user. To do that
-
- - Navigate to Dashboard on Koding from;
- `http://gitlab.**YOUR_IP**.nip.io:8090/Home/my-account`
- - Scroll down to Integrations section
- - Click on toggle to turn On integration in GitLab integration section
-
-This will redirect you to your GitLab instance and will ask your permission (
-if you are not logged in to GitLab at this point you will be redirected after
-login) once you accept you will be redirected to your Koding instance.
-
-From now on you can login by using `SIGN IN WITH GITLAB` button on your Login
-screen in your Koding instance.
-
-[ce-5909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5909
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index 293036f2f4b..b61c5409a56 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -80,10 +80,10 @@ our AsciiDoc snippets, wikis and repos using delimited blocks:
```
[plantuml, format="png", id="myDiagram", width="200px"]
- --
+ ----
Bob->Alice : hello
Alice -> Bob : Go Away
- --
+ ----
```
- **reStructuredText**
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 757865ea2c5..2feac1fd3b0 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -98,7 +98,7 @@ _The artifacts are stored by default in
If you don't want to use the local disk where GitLab is installed to store the
artifacts, you can use an object storage like AWS S3 instead.
This configuration relies on valid AWS credentials to be configured already.
-Use an [Object storage option][os] like AWS S3 to store job artifacts.
+Use an object storage option like AWS S3 to store job artifacts.
### Object Storage Settings
@@ -315,4 +315,3 @@ memory and disk I/O.
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab"
[restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab"
[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository"
-[os]: https://docs.gitlab.com/administration/job_artifacts.html#using-object-storage
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 7bc92ae77ee..c6fd7ef7360 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -45,9 +45,6 @@ The following metrics are available:
| redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded |
| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
-| filesystem_circuitbreaker_latency_seconds | Gauge | 9.5 | Time spent validating if a storage is accessible |
-| filesystem_circuitbreaker | Gauge | 9.5 | Whether or not the circuit for a certain shard is broken or not |
-| circuitbreaker_storage_check_duration_seconds | Histogram | 10.3 | Time a single storage probe took |
| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login |
| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login |
diff --git a/doc/administration/operations/filesystem_benchmarking.md b/doc/administration/operations/filesystem_benchmarking.md
new file mode 100644
index 00000000000..44018e966e0
--- /dev/null
+++ b/doc/administration/operations/filesystem_benchmarking.md
@@ -0,0 +1,55 @@
+# Filesystem Performance Benchmarking
+
+Filesystem performance has a big impact on overall GitLab performance,
+especially for actions that read or write to Git repositories. This information
+will help benchmark filesystem performance against known good and bad real-world
+systems.
+
+Normally when talking about filesystem performance the biggest concern is
+with Network Filesystems (NFS). However, even some local disks can have slow
+IO. The information on this page can be used for either scenario.
+
+## Write Performance
+
+The following one-line command is a quick benchmark for filesystem write
+performance. This will write 1,000 small files to the directory in which it is
+executed.
+
+1. Change into the root of the appropriate
+ [repository storage path](../repository_storage_paths.md).
+1. Create a temporary directory for the test so it's easy to remove the files later:
+
+ ```sh
+ mkdir test; cd test
+ ```
+1. Run the command:
+
+ ```sh
+ time for i in {0..1000}; do echo 'test' > "test${i}.txt"; done
+ ```
+1. Remove the test files:
+
+ ```sh
+ cd ../; rm -rf test
+ ```
+
+The output of the `time for ...` command will look similar to the following. The
+important metric is the `real` time.
+
+```sh
+$ time for i in {0..1000}; do echo 'test' > "test${i}.txt"; done
+
+real 0m0.116s
+user 0m0.025s
+sys 0m0.091s
+```
+
+From experience with multiple customers, the following are ranges that indicate
+whether your filesystem performance is satisfactory or less than ideal:
+
+| Rating | Benchmark result |
+|:----------|:------------------------|
+| Best | Less than 10 seconds |
+| OK | 10-18 seconds |
+| Poor | 18-25 seconds |
+| Very poor | Greater than 25 seconds |
diff --git a/doc/administration/operations/index.md b/doc/administration/operations/index.md
index dea98cb8197..a16fc7ae74f 100644
--- a/doc/administration/operations/index.md
+++ b/doc/administration/operations/index.md
@@ -16,3 +16,7 @@ to restart Sidekiq.
indexed lookup to the GitLab database](fast_ssh_key_lookup.md), and/or
by [doing away with user SSH keys stored on GitLab entirely in favor
of SSH certificates](ssh_certificates.md).
+- [Filesystem Performance Benchmarking](filesystem_benchmarking.md): Filesystem
+performance can have a big impact on GitLab performance, especially for actions
+that read or write Git repositories. This information will help benchmark
+filesystem performance against known good and bad real-world systems.
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index 7ea7ed48850..c03f23a8931 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -97,55 +97,6 @@ be stored via the **Application Settings** in the Admin area.
Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be
randomly placed on one of the selected paths.
-## Handling failing repository storage
-
-> [Introduced][ce-11449] in GitLab 9.5.
-
-When GitLab detects access to the repositories storage fails repeatedly, it can
-gracefully prevent attempts to access the storage. This might be useful when
-the repositories are stored somewhere on the network.
-
-This can be configured from the admin interface:
-
-![circuitbreaker configuration](img/circuitbreaker_config.png)
-
-**Number of access attempts**: The number of attempts GitLab will make to access a
-storage when probing a shard.
-
-**Number of failures before backing off**: The number of failures after which
-GitLab will start temporarily disabling access to a storage shard on a host.
-
-**Maximum git storage failures:** The number of failures of after which GitLab will
-completely prevent access to the storage. The number of failures can be reset in
-the admin interface: `https://gitlab.example.com/admin/health_check` or using the
-[api](../api/repository_storage_health.md) to allow access to the storage again.
-
-**Seconds to wait after a storage failure:** When access to a storage fails. GitLab
-will prevent access to the storage for the time specified here. This allows the
-filesystem to recover.
-
-**Seconds before reseting failure information:** The time in seconds GitLab will
-keep failure information. When no failures occur during this time, information about the
-mount is reset.
-
-**Seconds to wait for a storage access attempt:** The time in seconds GitLab will
-try to access storage. After this time a timeout error will be raised.
-
-To enable the circuitbreaker for repository storage you can flip the feature flag from a rails console:
-
-```
-Feature.enable('git_storage_circuit_breaker')
-```
-
-Alternatively it can be enabled by setting `true` in the `GIT_STORAGE_CIRCUIT_BREAKER` environment variable.
-This approach would be used when enabling the circuit breaker on a single host.
-
-When storage failures occur, this will be visible in the admin interface like this:
-
-![failing storage](img/failing_storage.png)
-
-To allow access to all storages, click the `Reset git storage health information` button.
-
[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578
[restart-gitlab]: restart_gitlab.md#installations-from-source
[reconfigure-gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
diff --git a/doc/api/events.md b/doc/api/events.md
index cd84b32029e..ccac5b8bb60 100644
--- a/doc/api/events.md
+++ b/doc/api/events.md
@@ -71,7 +71,7 @@ Parameters:
Example request:
```
-curl --header "PRIVATE-TOKEN 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/events&target_type=issue&action=created&after=2017-01-31&before=2017-03-01
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/events&target_type=issue&action=created&after=2017-01-31&before=2017-03-01
```
Example response:
@@ -276,7 +276,7 @@ Parameters:
Example request:
```
-curl --header "PRIVATE-TOKEN 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:project_id/events&target_type=issue&action=created&after=2017-01-31&before=2017-03-01
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:project_id/events&target_type=issue&action=created&after=2017-01-31&before=2017-03-01
```
Example response:
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index a4fdeca162e..f5ac3816fe5 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -216,7 +216,7 @@ GET /projects/:id/repository/merge_base
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-| `refs` | array | yes | The refs to find the common ancestor of, for now only 2 refs are supported |
+| `refs` | array | yes | The refs to find the common ancestor of, multiple refs can be passed |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/merge_base?refs[]=304d257dcb821665ab5110318fc58a007bd104ed&refs[]=0031876facac3f2b2702a0e53a26e89939a42209"
diff --git a/doc/api/repository_storage_health.md b/doc/api/repository_storage_health.md
index e0c0315c2d7..edf4b04acea 100644
--- a/doc/api/repository_storage_health.md
+++ b/doc/api/repository_storage_health.md
@@ -1,74 +1,5 @@
# Circuitbreaker API
-> [Introduced][ce-11449] in GitLab 9.5.
-
-The Circuitbreaker API is only accessible to administrators. All requests by
-guests will respond with `401 Unauthorized`, and all requests by normal users
-will respond with `403 Forbidden`.
-
-## Repository Storages
-
-### Get all storage information
-
-Returns of all currently configured storages and their health information.
-
-```
-GET /circuit_breakers/repository_storage
-```
-
-```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/circuit_breakers/repository_storage
-```
-
-```json
-[
- {
- "storage_name": "default",
- "failing_on_hosts": [],
- "total_failures": 0
- },
- {
- "storage_name": "broken",
- "failing_on_hosts": [
- "web01", "worker01"
- ],
- "total_failures": 1
- }
-]
-```
-
-### Get failing storages
-
-This returns a list of all currently failing storages.
-
-```
-GET /circuit_breakers/repository_storage/failing
-```
-
-```bash
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/circuit_breakers/repository_storage/failing
-```
-
-```json
-[
- {
- "storage_name":"broken",
- "failing_on_hosts":["web01", "worker01"],
- "total_failures":2
- }
-]
-```
-
-## Reset failing storage information
-
-Use this remove all failing storage information and allow access to the storage again.
-
-```
-DELETE /circuit_breakers/repository_storage
-```
-
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/circuit_breakers/repository_storage
-```
-
-[ce-11449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11449
+NOTE: **Deprecated:**
+Support of the circuit breaker is removed, as Gitaly can be configured to
+to work without NFS and [communicate solely over HTTP](../administration/gitaly/index.md).
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 1c41b3345ad..9b38e3a4eb7 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -1,3 +1,7 @@
+---
+table_display_block: true
+---
+
# Application settings API
These API calls allow you to read and modify GitLab instance
@@ -45,8 +49,6 @@ Example response:
"sign_in_text" : null,
"container_registry_token_expire_delay": 5,
"repository_storages": ["default"],
- "koding_enabled": false,
- "koding_url": null,
"plantuml_enabled": false,
"plantuml_url": null,
"terminal_max_session_time": 0,
@@ -103,8 +105,6 @@ Example response:
"after_sign_out_path": "",
"container_registry_token_expire_delay": 5,
"repository_storages": ["default"],
- "koding_enabled": false,
- "koding_url": null,
"plantuml_enabled": false,
"plantuml_url": null,
"terminal_max_session_time": 0,
@@ -138,11 +138,6 @@ are listed in the descriptions of the relevant settings.
| `authorized_keys_enabled` | boolean | no | 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 disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. |
| `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. |
| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It will automatically build, test, and deploy applications based on a predefined CI/CD configuration. |
-| `circuitbreaker_access_retries` | integer | no | The number of attempts GitLab will make to access a storage. |
-| `circuitbreaker_check_interval` | integer | no | Number of seconds in between storage checks. |
-| `circuitbreaker_failure_count_threshold` | integer | no | The number of failures after which GitLab will completely prevent access to the storage. |
-| `circuitbreaker_failure_reset_time` | integer | no | Time in seconds GitLab will keep storage failure information. When no failures occur during this time, the failure information is reset. |
-| `circuitbreaker_storage_timeout` | integer | no | Seconds to wait for a storage access attempt. |
| `clientside_sentry_dsn` | string | required by: `clientside_sentry_enabled` | Clientside Sentry Data Source Name. |
| `clientside_sentry_enabled` | boolean | no | (**If enabled, requires:** `clientside_sentry_dsn`) Enable Sentry error reporting for the client side. |
| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes. |
@@ -180,8 +175,6 @@ are listed in the descriptions of the relevant settings.
| `html_emails_enabled` | boolean | no | Enable HTML emails. |
| `instance_statistics_visibility_private` | boolean | no | When set to `true` Instance statistics will only be available to admins. |
| `import_sources` | array of strings | no | Sources to allow project import from, possible values: `github`, `bitbucket`, `gitlab`, `google_code`, `fogbugz`, `git`, and `gitlab_project`. |
-| `koding_enabled` | boolean | no | (If enabled, requires: `koding_url`) Enable Koding integration. Default is `false`. |
-| `koding_url` | string | required by: `koding_enabled` | The Koding instance URL for integration. |
| `max_artifacts_size` | integer | no | Maximum artifacts size in MB |
| `max_attachment_size` | integer | no | Limit attachment size in MB |
| `max_pages_size` | integer | no | Maximum size of pages repositories in MB |
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
index ab429e0ded3..70020d461d9 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
@@ -125,7 +125,7 @@ They can be added per project by navigating to the project's **Settings** > **CI
To the field **KEY**, add the name `SSH_PRIVATE_KEY`, and to the **VALUE** field, paste the private key you've copied earlier.
We'll use this variable in the `.gitlab-ci.yml` later, to easily connect to our remote server as the deployer user without entering its password.
-We also need to add the public key to **Project** > **Settings** > **Repository** as [Deploy Keys](../../../ssh/README.md/#deploy-keys), which gives us the ability to access our repository from the server through [SSH protocol](../../../gitlab-basics/command-line-commands.md/#start-working-on-your-project).
+We also need to add the public key to **Project** > **Settings** > **Repository** as [Deploy Keys](../../../ssh/README.md#deploy-keys), which gives us the ability to access our repository from the server through [SSH protocol](../../../gitlab-basics/command-line-commands.md#start-working-on-your-project).
```bash
@@ -378,7 +378,7 @@ These are persistent data and will be shared to every new release.
Now, we would need to deploy our app by running `envoy run deploy`, but it won't be necessary since GitLab can handle that for us with CI's [environments](../../environments.md), which will be described [later](#setting-up-gitlab-ci-cd) in this tutorial.
Now it's time to commit [Envoy.blade.php](https://gitlab.com/mehranrasulian/laravel-sample/blob/master/Envoy.blade.php) and push it to the `master` branch.
-To keep things simple, we commit directly to `master`, without using [feature-branches](../../../workflow/gitlab_flow.md/#github-flow-as-a-simpler-alternative) since collaboration is beyond the scope of this tutorial.
+To keep things simple, we commit directly to `master`, without using [feature-branches](../../../workflow/gitlab_flow.md#github-flow-as-a-simpler-alternative) since collaboration is beyond the scope of this tutorial.
In a real world project, teams may use [Issue Tracker](../../../user/project/issues/index.md) and [Merge Requests](../../../user/project/merge_requests/index.md) to move their code across branches:
```bash
@@ -398,7 +398,7 @@ In the case you're not familiar with Docker, refer to [How to Automate Docker De
To be able to build, test, and deploy our app with GitLab CI/CD, we need to prepare our work environment.
To do that, we'll use a Docker image which has the minimum requirements that a Laravel app needs to run.
-[There are other ways](../php.md/#test-php-projects-using-the-docker-executor) to do that as well, but they may lead our builds run slowly, which is not what we want when there are faster options to use.
+[There are other ways](../php.md#test-php-projects-using-the-docker-executor) to do that as well, but they may lead our builds run slowly, which is not what we want when there are faster options to use.
With Docker images our builds run incredibly faster!
@@ -536,7 +536,7 @@ That's a lot to take in, isn't it? Let's run through it step by step.
[GitLab Runners](../../runners/README.md) run the script defined by `.gitlab-ci.yml`.
The `image` keyword tells the Runners which image to use.
-The `services` keyword defines additional images [that are linked to the main image](../../docker/using_docker_images.md/#what-is-a-service).
+The `services` keyword defines additional images [that are linked to the main image](../../docker/using_docker_images.md#what-is-a-service).
Here we use the container image we created before as our main image and also use MySQL 5.7 as a service.
```yaml
@@ -560,7 +560,7 @@ So we should adjust the configuration of MySQL instance by defining `MYSQL_DATAB
Find out more about MySQL variables at the [official MySQL Docker Image](https://hub.docker.com/r/_/mysql/).
Also set the variables `DB_HOST` to `mysql` and `DB_USERNAME` to `root`, which are Laravel specific variables.
-We define `DB_HOST` as `mysql` instead of `127.0.0.1`, as we use MySQL Docker image as a service which [is linked to the main Docker image](../../docker/using_docker_images.md/#how-services-are-linked-to-the-build).
+We define `DB_HOST` as `mysql` instead of `127.0.0.1`, as we use MySQL Docker image as a service which [is linked to the main Docker image](../../docker/using_docker_images.md#how-services-are-linked-to-the-build).
```yaml
...
@@ -602,7 +602,7 @@ unit_test:
#### Deploy to production
The job `deploy_production` will deploy the app to the production server.
-To deploy our app with Envoy, we had to set up the `$SSH_PRIVATE_KEY` variable as an [SSH private key](../../ssh_keys/README.md/#ssh-keys-when-using-the-docker-executor).
+To deploy our app with Envoy, we had to set up the `$SSH_PRIVATE_KEY` variable as an [SSH private key](../../ssh_keys/README.md#ssh-keys-when-using-the-docker-executor).
If the SSH keys have added successfully, we can run Envoy.
As mentioned before, GitLab supports [Continuous Delivery](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-delivery) methods as well.
diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md
index df4805ea7ac..c1048f3d2e3 100644
--- a/doc/ci/examples/php.md
+++ b/doc/ci/examples/php.md
@@ -20,7 +20,7 @@ build environment.
Let's first specify the PHP image that will be used for the job process
(you can read more about what an image means in the Runner's lingo reading
-about [Using Docker images](../docker/using_docker_images.md#what-is-image)).
+about [Using Docker images](../docker/using_docker_images.md#what-is-an-image)).
Start by adding the image to your `.gitlab-ci.yml`:
diff --git a/doc/ci/img/pipeline_incremental_rollout.png b/doc/ci/img/pipeline_incremental_rollout.png
new file mode 100644
index 00000000000..b3498e9a5a5
--- /dev/null
+++ b/doc/ci/img/pipeline_incremental_rollout.png
Binary files differ
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index ea47d676edb..371703a12c8 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -193,6 +193,18 @@ stage has a job with a manual action.
![Pipelines example](img/pipelines.png)
+### Delay a particular job in the pipeline graph
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4.
+
+When you do not want to run a job immediately, you can [delay the job to run after a certain period](yaml/README.md#when-delayed).
+This is especially useful for timed incremental rollout that new code is rolled out gradually.
+For example, if you start rolling out new code and users do not experience trouble, GitLab automatically completes the deployment from 0% to 100%.
+Alternatively, if you start rolling out and you noticed that a few users experience trouble with the version,
+you can stop the timed incremental rollout by canceling the pipeline, and [rolling](environments.md#rolling-back-changes) it back to the stable version.
+
+![Pipelines example](img/pipeline_incremental_rollout.png)
+
### Ordering of jobs in pipeline graphs
**Regular pipeline graph**
@@ -211,6 +223,7 @@ by name. The order of severity is:
- pending
- running
- manual
+- scheduled
- canceled
- success
- skipped
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index cf92d90ba30..bffb0121603 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -2,7 +2,7 @@
> **Notes**:
>
-> - [Introduced][ci-229] in GitLab CE 7.14.
+> - [Introduced](https://about.gitlab.com/2015/08/22/gitlab-7-14-released/) in GitLab 7.14.
> - GitLab 8.12 has a completely redesigned job permissions system. Read all
> about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#job-triggers).
@@ -154,10 +154,10 @@ This information is also exposed in the UI.
Using trigger variables can be proven useful for a variety of reasons:
-* Identifiable jobs. Since the variable is exposed in the UI you can know
+- Identifiable jobs. Since the variable is exposed in the UI you can know
why the rebuild was triggered if you pass a variable that explains the
purpose.
-* Conditional job processing. You can have conditional jobs that run whenever
+- Conditional job processing. You can have conditional jobs that run whenever
a certain variable is present.
Consider the following `.gitlab-ci.yml` where we set three
@@ -221,7 +221,6 @@ removed with one of the future versions of GitLab. You are advised to
[take ownership](#taking-ownership) of any legacy triggers.
[ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017
-[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229
[ee]: https://about.gitlab.com/pricing/
[variables]: ../variables/README.md
[predef]: ../variables/README.md#predefined-variables-environment-variables
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 8b770495853..24d60a0cdcc 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -415,7 +415,7 @@ Four keys are now available: `refs`, `kubernetes` and `variables` and `changes`.
Refs strategy equals to simplified only/except configuration, whereas
kubernetes strategy accepts only `active` keyword.
-### `variables`
+### `only:variables`
`variables` keyword is used to define variables expressions. In other words
you can use predefined variables / project / group or
@@ -460,7 +460,7 @@ end-to-end:
Learn more about variables expressions on [a separate page][variables-expressions].
-### `changes`
+### `only:changes`
Using `changes` keyword with `only` or `except` makes it possible to define if
a job should be created based on files modified by a git push event.
@@ -673,6 +673,42 @@ user wants to trigger an action. In other words, in order to trigger a manual
action assigned to a branch that the pipeline is running for, user needs to
have ability to merge to this branch.
+### `when:delayed`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4.
+
+Delayed job are for executing scripts after a certain period.
+This is useful if you want to avoid jobs entering `pending` state immediately.
+
+You can set the period with `start_in` key. The value of `start_in` key is an elapsed time in seconds, unless a unit is
+provided. `start_key` must be less than or equal to one hour. Examples of valid values include:
+
+- `10 seconds`
+- `30 minutes`
+- `1 hour`
+
+When there is a delayed job in a stage, the pipeline will not progress until the delayed job has finished.
+This means this keyword can also be used for inserting delays between different stages.
+
+The timer of a delayed job starts immediately after the previous stage has completed.
+Similar to other types of jobs, a delayed job's timer will not start unless the previous stage passed.
+
+The following example creates a job named `timed rollout 10%` that is executed 30 minutes after the previous stage has completed:
+
+```yaml
+timed rollout 10%:
+ stage: deploy
+ script: echo 'Rolling out 10% ...'
+ when: delayed
+ start_in: 30 minutes
+```
+
+You can stop the active timer of a delayed job by clicking the **Unschedule** button.
+This job will never be executed in the future unless you execute the job manually.
+
+You can start a delayed job immediately by clicking the **Play** button.
+GitLab runner will pick your job soon and start the job.
+
## `environment`
> **Notes:**
diff --git a/doc/development/README.md b/doc/development/README.md
index 43d3865da0e..14dfe8eb1f3 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -8,7 +8,7 @@ description: 'Learn how to contribute to GitLab.'
## Get started!
- Set up GitLab's development environment with [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/README.md)
-- [GitLab contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md)
+- [GitLab contributing guide](contributing/index.md)
- [Architecture](architecture.md) of GitLab
- [Rake tasks](rake_tasks.md) for development
@@ -50,6 +50,7 @@ description: 'Learn how to contribute to GitLab.'
- [Permissions](permissions.md)
- [Prometheus metrics](prometheus_metrics.md)
- [Guidelines for reusing abstractions](reusing_abstractions.md)
+- [DeclarativePolicy framework](policies.md)
## Performance guides
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index fac31fe8e8a..4d3a817e78b 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -141,6 +141,20 @@ first time.
branch. Do not squash until the branch is ready to merge. Reviewers should be
able to read individual updates based on their earlier feedback.
+### Assigning a merge request for a review
+
+If you want to have your merge request reviewed you can assign it to any reviewer. The list of reviewers can be found on [Engineering projects](https://about.gitlab.com/handbook/engineering/projects/) page.
+
+You can also use `ready for review` label. That means that your merge request is ready to be reviewed and any reviewer can pick it. It is recommended to use that label only if there isn't time pressure and make sure the merge request is assigned to a reviewer.
+
+When your merge request was reviewed and can be passed to a maintainer you can either pick a specific maintainer or use a label `ready for merge`.
+
+It is responsibility of the author of a merge request that the merge request is reviewed. If it stays in `ready for review` state too long it is recommended to assign it to a specific reviewer.
+
+### List of merge requests ready for review
+
+Developers who have capacity can regularly check the list of [merge requests to review](https://gitlab.com/groups/gitlab-org/-/merge_requests?scope=all&utf8=%E2%9C%93&state=opened&label_name%5B%5D=ready%20for%20review) and assign any merge request they want to review.
+
### Reviewing code
Understand why the change is necessary (fixes a bug, improves the user
diff --git a/doc/development/contributing/community_roles.md b/doc/development/contributing/community_roles.md
index c508969f7f4..b9c369286d2 100644
--- a/doc/development/contributing/community_roles.md
+++ b/doc/development/contributing/community_roles.md
@@ -9,4 +9,8 @@ GitLab community members and their privileges/responsibilities.
| Developer |Has access to GitLab internal infrastructure & issues (e.g. HR-related) | GitLab employee or a Core Team member (with an NDA) |
| Contributor | Can make contributions to all GitLab public projects | Have a GitLab.com account |
-[List of current reviewers/maintainers](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce) \ No newline at end of file
+[List of current reviewers/maintainers](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce).
+
+---
+
+[Return to Contributing documentation](index.md)
diff --git a/doc/development/contributing/design.md b/doc/development/contributing/design.md
index be7891061f9..79750878aac 100644
--- a/doc/development/contributing/design.md
+++ b/doc/development/contributing/design.md
@@ -13,7 +13,10 @@ There is a special type label called ~"product discovery". It represents a disco
~"product discovery" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone.
-The initial issue should be about the problem we are solving. If a separate [product discovery issue](#product-discovery-issues) is needed for additional research and design work, it will be created by a PM or UX person. Assign the ~UX, ~"product discovery" and ~"Deliverable" labels, add a milestone and use a title that makes it clear that the scheduled issue is product discovery
+The initial issue should be about the problem we are solving. If a separate [product discovery issue](https://about.gitlab.com/handbook/engineering/ux/ux-department-workflow/#how-we-use-labels)
+is needed for additional research and design work, it will be created by a PM or UX person.
+Assign the ~UX, ~"product discovery" and ~"Deliverable" labels, add a milestone and
+use a title that makes it clear that the scheduled issue is product discovery
(e.g. `Product discovery for XYZ`).
In order to complete a product discovery issue in a release, you must complete the following:
@@ -23,34 +26,6 @@ In order to complete a product discovery issue in a release, you must complete t
1. Copy the design to the description of the delivery issue for which the product discovery issue was created. Do not simply refer to the product discovery issue as a separate source of truth.
1. In some cases, a product discovery issue also identifies future enhancements that will not go into the issue that originated the product discovery issue. For these items, create new issues containing the designs to ensure they are not lost. Put the issues in the backlog if they are agreed upon as good ideas. Otherwise leave them for triage.
-## Style guides
-
-1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
- Important sections include [Source Code Layout][rss-source] and
- [Naming][rss-naming]. Use:
- - multi-line method chaining style **Option A**: dot `.` on the second line
- - string literal quoting style **Option A**: single quoted by default
-1. [Rails](https://github.com/bbatsov/rails-style-guide)
-1. [Newlines styleguide][newlines-styleguide]
-1. [Testing][testing]
-1. [JavaScript styleguide][js-styleguide]
-1. [SCSS styleguide][scss-styleguide]
-1. [Shell commands](../shell_commands.md) created by GitLab
- contributors to enhance security
-1. [Database Migrations](../migration_style_guide.md)
-1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
-1. [Documentation styleguide](https://docs.gitlab.com/ee/development/documentation/styleguide.html)
-1. Interface text should be written subjectively instead of objectively. It
- should be the GitLab core team addressing a person. It should be written in
- present time and never use past tense (has been/was). For example instead
- of _prohibited this user from being saved due to the following errors:_ the
- text should be _sorry, we could not create your account because:_
-1. Code should be written in [US English][us-english]
-
-This is also the style used by linting tools such as
-[RuboCop](https://github.com/bbatsov/rubocop),
-[PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
-
---
[Return to Contributing documentation](index.md)
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index f4486ae3549..29af8dcb9bb 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -1,22 +1,24 @@
# Contribute to GitLab
-For a first-time step-by-step guide to the contribution process, see
-["Contributing to GitLab"](https://about.gitlab.com/contributing/).
-
Thank you for your interest in contributing to GitLab. This guide details how
-to contribute to GitLab in a way that is efficient for everyone.
+to contribute to GitLab in a way that is easy for everyone.
+
+For a first-time step-by-step guide to the contribution process, please see
+["Contributing to GitLab"](https://about.gitlab.com/contributing/).
-Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute).
+Looking for something to work on? Look for issues in the [Backlog (Accepting merge requests) milestone](#i-want-to-contribute).
-GitLab comes into two flavors, GitLab Community Edition (CE) our free and open
+GitLab comes in two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial
edition. Throughout this guide you will see references to CE and EE for
abbreviation.
-If you have read this guide and want to know how the GitLab [core team]
+To get an overview of GitLab community membership including those that would be reviewing or merging your contributions, please visit [the community roles page](community_roles.md).
+
+If you want to know how the GitLab [core team]
operates please see [the GitLab contributing process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md).
-- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
+[GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
## Security vulnerability disclosure
@@ -28,33 +30,77 @@ vulnerabilities.
## Code of conduct
-As contributors and maintainers of this project, we pledge to respect all
-people who contribute through reporting issues, posting feature requests,
-updating documentation, submitting pull requests or patches, and other
-activities.
+### Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+### Our Standards
-We are committed to making participation in this project a harassment-free
-experience for everyone, regardless of level of experience, gender, gender
-identity and expression, sexual orientation, disability, personal appearance,
-body size, race, ethnicity, age, or religion.
+Examples of behavior that contributes to creating a positive environment
+include:
-Examples of unacceptable behavior by participants include the use of sexual
-language or imagery, derogatory comments or personal attacks, trolling, public
-or private harassment, insults, or other unprofessional conduct.
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+### Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct. Project maintainers who do not
-follow the Code of Conduct may be removed from the project team.
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+### Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+### Enforcement
-This code of conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community.
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at conduct@gitlab.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
-Instances of abusive, harassing, or otherwise unacceptable behavior can be
-reported by emailing `contact@gitlab.com`.
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
-This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
-available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
+### Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
## Closing policy for issues and merge requests
@@ -87,8 +133,8 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute!
-If you want to contribute to GitLab [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight]
-is a great place to start. Issues with a lower weight (1 or 2) are deemed
+If you want to contribute to GitLab, [issues in the `Backlog (Accepting merge requests)` milestone][accepting-mrs-weight]
+are a great place to start. Issues with a lower weight (1 or 2) are deemed
suitable for beginners. These issues will be of reasonable size and challenge,
for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
@@ -117,93 +163,39 @@ When your code contains more than 500 changes, any major breaking changes, or an
This [documentation](issue_workflow.md) outlines the current workflow labels.
-### Type labels
-
-This [documentation](issue_workflow.md) outlines the current type labels.
-
-### Subject labels
-
-This [documentation](issue_workflow.md) outlines the current subject labels.
-
-### Team labels
-
-This [documentation](issue_workflow.md) outlines the current team labels.
-
-### Milestone labels
-
-This [documentation](issue_workflow.md) outlines the current milestone labels.
-
-### Bug Priority labels
-
-This [documentation](issue_workflow.md) outlines the current bug priority labels.
-
-### Bug Severity labels
-
-This [documentation](issue_workflow.md) outlines the current severity labels.
-
-#### Severity impact guidance
-
-This [documentation](issue_workflow.md) outlines the current severity impact guidance.
-
-### Label for community contributors
-
-This [documentation](issue_workflow.md) outlines the current policy regarding community contributor issues.
-
-## Implement design & UI elements
-
-This [documentation](design.md) outlines the current design and UI guidelines.
-
-## Issue tracker
-
-This [documentation](issue_workflow.md) outlines the issue tracker process.
-
-### Issue triaging
-
-This [documentation](issue_workflow.md) outlines the current issue triaging process.
-
-### Feature proposals
-
-This [documentation](issue_workflow.md) outlines the feature proposal process.
-
-### Issue tracker guidelines
-
-This [documentation](issue_workflow.md) outlines the issue tracker guidelines.
-
-### Issue weight
-
-This [documentation](issue_workflow.md) outlines the issue weight guidelines.
-
-### Regression issues
-
-This [documentation](issue_workflow.md) outlines the regression issue process.
-
-### Technical and UX debt
-
-This [documentation](issue_workflow.md) about technical and UX debt has been moved.
-
-### Stewardship
-
-This [documentation](issue_workflow.md) outlines the stewardship process.
+* [Type labels](issue_workflow.md#type-labels)
+* [Subject labels](issue_workflow.md#subject-labels)
+* [Team labels](issue_workflow.md#team-labels)
+* [Release Scoping labels](issue_workflow.md#release-scoping-labels)
+* [Priority labels](issue_workflow.md#priority-labels)
+* [Severity labels](issue_workflow.md#severity-labels)
+* [Label for community contributors](issue_workflow.md#label-for-community-contributors)
+* [Issue triaging](issue_workflow.md#issue-triaging)
+* [Feature proposals](issue_workflow.md#feature-proposals)
+* [Issue tracker guidelines](issue_workflow.md#issue-tracker-guidelines)
+* [Issue weight](issue_workflow.md#issue-weight)
+* [Regression issues](issue_workflow.md#regression-issues)
+* [Technical and UX debt](issue_workflow.md#technical-and-ux-debt)
+* [Stewardship](issue_workflow.md#stewardship)
## Merge requests
This [documentation](merge_request_workflow.md) outlines the current merge request process.
-### Merge request guidelines
-
-This [documentation](merge_request_workflow.md) outlines the current merge request guidelines.
-
-### Contribution acceptance criteria
-
-This [documentation](merge_request_workflow.md) outlines the current acceptance criteria for contributions.
-
-## Definition of done
-
-This [documentation](merge_request_workflow.md) outlines the definition of done.
+* [Merge request guidelines](merge_request_workflow.md#merge-request-guidelines)
+* [Contribution acceptance criteria](merge_request_workflow.md#contribution-acceptance-criteria)
+* [Definition of done](merge_request_workflow.md#definition-of-done)
## Style guides
-This [documentation](design.md) outlines the current style guidelines.
+
+This [documentation](style_guides.md) outlines the current style guidelines.
---
[Return to Development documentation](../README.md)
+
+[core team]: https://about.gitlab.com/core-team/
+[team]: https://about.gitlab.com/team/
+[getting-help]: https://about.gitlab.com/getting-help/
+[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
+[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?scope=all&utf8=✓&state=opened&assignee_id=0&milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 2f06677bfec..cd5eee6ea36 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -9,6 +9,7 @@ Most issues will have labels for at least one of the following:
- Type: ~"feature proposal", ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~"CI/CD", ~Plan, ~Manage, ~Quality, etc.
+- Stage: ~"devops:plan", ~"devops:create", etc.
- Release Scoping: ~Deliverable, ~Stretch, ~"Next Patch Release"
- Priority: ~P1, ~P2, ~P3, ~P4
- Severity: ~S1, ~S2, ~S3, ~S4
@@ -20,7 +21,6 @@ If you come across an issue that has none of these, and you're allowed to set
labels, you can _always_ add the team and type, and often also the subject.
[milestones-page]: https://gitlab.com/gitlab-org/gitlab-ce/milestones
-[labels-page]: https://gitlab.com/gitlab-org/gitlab-ce/labels
## Type labels
@@ -84,6 +84,39 @@ indicate if an issue needs backend work, frontend work, or both.
Team labels are always capitalized so that they show up as the first label for
any issue.
+## Stage labels
+
+Stage labels specify which [DevOps stage][devops-stages] the issue belongs to.
+
+The current stage labels are:
+
+- ~"devops:manage"
+- ~"devops:plan"
+- ~"devops:create"
+- ~"devops:verify"
+- ~"devops:package"
+- ~"devops:release"
+- ~"devops:configure"
+- ~"devops:monitor"
+- ~"devops:secure"
+
+These labels should be mutually exclusive. If an issue belongs to multiple
+stages, the most relevant should be used.
+
+They differ from the [Team labels](#team-labels) because teams may work on
+issues outside their stage.
+
+Normally there is a 1:1 relationship between Stage labels and Team labels, but
+any issue can be picked up by any team, depending on current priorities.
+So, an issue labeled ~"devops:create" may be scheduled by the ~Plan team, for
+example. In such cases, it's usual to include both team labels so each team can
+be aware of the progress.
+
+The Stage labels are used to generate the [direction pages][direction-pages] automatically.
+
+[devops-stages]: https://about.gitlab.com/direction/#devops-stages
+[direction-pages]: https://about.gitlab.com/direction/
+
## Release Scoping labels
Release Scoping labels help us clearly communicate expectations of the work for the
@@ -108,12 +141,12 @@ Priority labels help us define the time a ~bug fix should be completed. Priority
If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes.
-| Label | Meaning | Estimate time to fix |
-|-------|-----------------|------------------------------------------------------------------|
-| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com |
-| ~P2 | High Priority | The next release |
-| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) |
-| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) |
+| Label | Meaning | Defect SLA (applies only to ~bug and ~security defects) |
+|-------|-----------------|----------------------------------------------------------------------------|
+| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com (30 days) |
+| ~P2 | High Priority | The next release (60 days) |
+| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter or 90 days) |
+| ~P4 | Low Priority | Anything outside the next 3 releases (more than one quarter or 120 days) |
## Severity labels
@@ -208,6 +241,7 @@ project.
[GitLab Triage]: https://gitlab.com/gitlab-org/gitlab-triage
[scheduled pipeline]: https://gitlab.com/gitlab-org/quality/triage-ops/pipeline_schedules/10512/edit
[quality/triage-ops]: https://gitlab.com/gitlab-org/quality/triage-ops
+[team]: https://about.gitlab.com/team/
## Feature proposals
@@ -235,6 +269,8 @@ need to ask one of the [core team] members to add the label, if you do not have
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.
+[fpl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature+proposal
+
## Issue tracker guidelines
**[Search the issue tracker][ce-tracker]** for similar entries before
@@ -331,3 +367,7 @@ A recent example of this was the issue for
---
[Return to Contributing documentation](index.md)
+
+[labels-page]: https://gitlab.com/gitlab-org/gitlab-ce/labels
+[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
+[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index a286e74908c..cc7d8a1e1db 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -2,9 +2,9 @@
We welcome merge requests with fixes and improvements to GitLab code, tests,
and/or documentation. The issues that are specifically suitable for
-community contributions are listed with the label
-[`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
-and [EE][accepting-mrs-ee], but you are free to contribute to any other issue
+community contributions are listed with the
+[`Backlog (Accepting merge requests)` milestone in the CE issue tracker][accepting-mrs-ce]
+and [EE issue tracker][accepting-mrs-ee], but you are free to contribute to any other issue
you want.
Please note that if an issue is marked for the current milestone either before
@@ -19,11 +19,16 @@ wireframes if the feature will also change the UI.
Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
If you are new to GitLab development (or web development in general), see the
-[I want to contribute!](#i-want-to-contribute) section to get you started with
+[I want to contribute!](index.md#i-want-to-contribute) section to get you started with
some potentially easy issues.
To start with GitLab development download the [GitLab Development Kit][gdk] and
-see the [Development section](../README.md) for some guidelines.
+see the [Development section](../../README.md) for some guidelines.
+
+[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
+[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
+[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
+[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
## Merge request guidelines
@@ -103,6 +108,10 @@ Please ensure that your merge request meets the contribution acceptance criteria
When having your code reviewed and when reviewing merge requests please take the
[code review guidelines](../code_review.md) into account.
+[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
+[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
+[team]: https://about.gitlab.com/team/
+
## Contribution acceptance criteria
1. The change is as small as possible
@@ -133,7 +142,7 @@ When having your code reviewed and when reviewing merge requests please take the
[polling with ETag caching][polling-etag].
1. Changes after submitting the merge request should be in separate commits
(no squashing).
-1. It conforms to the [style guides](#style-guides) and the following:
+1. It conforms to the [style guides](style_guides.md) and the following:
- If your change touches a line that does not follow the style, modify the
entire line to follow it. This prevents linting tools from generating warnings.
- Don't touch neighbouring lines. As an exception, automatic mass
@@ -144,6 +153,9 @@ When having your code reviewed and when reviewing merge requests please take the
"license-finder" test with a "Dependencies that need approval" error.
1. The merge request meets the [definition of done](#definition-of-done).
+[license-finder-doc]: ../licensing.md
+[polling-etag]: ../polling.md
+
## Definition of done
If you contribute to GitLab please know that changes involve more than just
@@ -156,7 +168,7 @@ the feature you contribute through all of these steps.
1. Performance/scalability implications have been considered, addressed, and tested
1. [Documented][doc-guidelines] in the `/doc` directory
1. [Changelog entry added][changelog], if necessary
-1. Reviewed and any concerns are addressed
+1. Reviewed by UX/FE/BE and any concerns are addressed
1. Merged by a project maintainer
1. Added to the release blog article, if relevant
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/), if relevant
@@ -175,6 +187,12 @@ merge request:
1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
+[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
+[testing]: ../testing_guide/index.md
+
---
[Return to Contributing documentation](index.md)
+
+[changelog]: ../changelog.md "Generate a changelog entry"
+[doc-guidelines]: ../documentation/index.md "Documentation guidelines"
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
new file mode 100644
index 00000000000..fb0454db7d2
--- /dev/null
+++ b/doc/development/contributing/style_guides.md
@@ -0,0 +1,40 @@
+# Style guides
+
+1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
+ Important sections include [Source Code Layout][rss-source] and
+ [Naming][rss-naming]. Use:
+ - multi-line method chaining style **Option A**: dot `.` on the second line
+ - string literal quoting style **Option A**: single quoted by default
+1. [Rails](https://github.com/bbatsov/rails-style-guide)
+1. [Newlines styleguide][newlines-styleguide]
+1. [Testing][testing]
+1. [JavaScript styleguide][js-styleguide]
+1. [SCSS styleguide][scss-styleguide]
+1. [Shell commands](../shell_commands.md) created by GitLab
+ contributors to enhance security
+1. [Database Migrations](../migration_style_guide.md)
+1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
+1. [Documentation styleguide](../documentation/styleguide.md)
+1. Interface text should be written subjectively instead of objectively. It
+ should be the GitLab core team addressing a person. It should be written in
+ present time and never use past tense (has been/was). For example instead
+ of _prohibited this user from being saved due to the following errors:_ the
+ text should be _sorry, we could not create your account because:_
+1. Code should be written in [US English][us-english]
+
+This is also the style used by linting tools such as
+[RuboCop](https://github.com/bbatsov/rubocop),
+[PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
+
+---
+
+[Return to Contributing documentation](index.md)
+
+[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
+[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
+[doc-guidelines]: ../documentation/index.md "Documentation guidelines"
+[js-styleguide]: ../fe_guide/style_guide_js.md "JavaScript styleguide"
+[scss-styleguide]: ../fe_guide/style_guide_scss.md "SCSS styleguide"
+[newlines-styleguide]: ../newlines_styleguide.md "Newlines styleguide"
+[testing]: ../testing_guide/index.md
+[us-english]: https://en.wikipedia.org/wiki/American_English
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 2db78e4a365..1dcdf788a3e 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -321,7 +321,7 @@ The following sample `markdownlint` configuration modifies the available default
}
```
-For [`markdownlint`](https://gitahub.com/DavidAnson/markdownlint/), this configuration must be
+For [`markdownlint`](https://github.com/DavidAnson/markdownlint/), this configuration must be
placed in a [valid location](https://github.com/igorshubovych/markdownlint-cli#configuration). For
example, `~/.markdownlintrc`.
@@ -414,7 +414,7 @@ to EE only.
NOTE: **Note:**
To preview your changes to documentation locally, follow this
-[development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development).
+[development guide](https://gitlab.com/gitlab-com/gitlab-docs/blob/master/README.md#development-when-contributing-to-gitlab-documentation) or [these instructions for GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/gitlab_docs.md).
The live preview is currently enabled for the following projects:
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index 656ddd868cd..1e0529262ad 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -1,11 +1,13 @@
# Style guides and linting
+
See the relevant style guides for our guidelines and for information on linting:
## JavaScript
+
We defer to [Airbnb][airbnb-js-style-guide] on most style-related
conventions and enforce them with eslint.
-See [our current .eslintrc][eslintrc] for specific rules and patterns.
+See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.eslintrc.yml) for specific rules and patterns.
### Common
@@ -21,10 +23,10 @@ refactor an existing one, you should abide by the eslint rules.
```javascript
// bad
/* eslint-disable */
-
+
// better
/* eslint-disable some-rule, some-other-rule */
-
+
// best
// nothing :)
```
@@ -34,14 +36,14 @@ refactor an existing one, you should abide by the eslint rules.
```javascript
// bad
/* eslint-disable no-new */
-
+
import Foo from 'foo';
-
+
new Foo();
-
+
// better
import Foo from 'foo';
-
+
// eslint-disable-next-line no-new
new Foo();
```
@@ -58,11 +60,11 @@ followed by any global declarations, then a blank newline prior to any imports o
/* global Foo */
/* eslint-disable no-new */
import Bar from './bar';
-
+
// good
/* eslint-disable no-new */
/* global Foo */
-
+
import Bar from './bar';
```
@@ -73,7 +75,7 @@ followed by any global declarations, then a blank newline prior to any imports o
```javascript
// bad
/* globals Flash, Cookies, jQuery */
-
+
// good
/* global Flash */
/* global Cookies */
@@ -85,7 +87,7 @@ followed by any global declarations, then a blank newline prior to any imports o
```javascript
// bad
fn(p1, p2, p3, p4) {}
-
+
// good
fn(options) {}
```
@@ -191,28 +193,28 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod
```javascript
// bad
const values = {foo: 1};
-
+
function impureFunction(items) {
const bar = 1;
-
+
items.foo = items.a * bar + 2;
-
+
return items.a;
}
-
+
const c = impureFunction(values);
-
+
// good
var values = {foo: 1};
-
+
function pureFunction (foo) {
var bar = 1;
-
+
foo = foo * bar + 2;
-
+
return foo;
}
-
+
var c = pureFunction(values.foo);
```
@@ -231,10 +233,10 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod
document.addEventListener('click', this.handleCallback)
},
handleCallback() {
-
+
}
}
-
+
// Good
export class Foo {
constructor() {
@@ -253,12 +255,12 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod
```javascript
const users = [ { name: 'Foo' }, { name: 'Bar' } ];
-
+
// bad
users.forEach((user, index) => {
user.id = index;
});
-
+
// good
const usersWithId = users.map((user, index) => {
return Object.assign({}, user, { id: index });
@@ -272,10 +274,10 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod
```javascript
// bad
+'10' // 10
-
+
// good
Number('10') // 10
-
+
// better
parseInt('10', 10);
```
@@ -289,7 +291,7 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod
<button class="add-user">
Add User
</button>
-
+
// good
<button class="js-add-user">
Add User
@@ -299,10 +301,12 @@ Do not use them anymore and feel free to remove them when refactoring legacy cod
### 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
1. Use a function in the bundle file to instantiate the Vue component:
@@ -314,7 +318,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
new Component({})
}
}
-
+
// good
document.addEventListener('DOMContentLoaded', () => new Vue({
el: '#element',
@@ -336,7 +340,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
}
}
}
-
+
// good
class Store {
constructor() {
@@ -354,14 +358,14 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
```javascript
// bad
import cardBoard from 'cardBoard.vue'
-
+
components: {
cardBoard,
};
-
+
// good
import CardBoard from 'cardBoard.vue'
-
+
components: {
CardBoard,
};
@@ -373,13 +377,13 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
```javascript
// bad
<component class="btn">
-
+
// good
<component css-class="btn">
-
+
// bad
<component myProp="prop" />
-
+
// good
<component my-prop="prop" />
```
@@ -387,6 +391,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
[#34371]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34371
#### Alignment
+
1. Follow these alignment styles for the template method:
1. With more than one attribute, all attributes should be on a new line:
@@ -395,31 +400,31 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
// bad
<component v-if="bar"
param="baz" />
-
+
<button class="btn">Click me</button>
-
+
// good
<component
v-if="bar"
param="baz"
/>
-
+
<button class="btn">
Click me
</button>
```
-
+
1. The tag can be inline if there is only one attribute:
```javascript
// good
<component bar="bar" />
-
+
// good
<component
bar="bar"
/>
-
+
// bad
<component
bar="bar" />
@@ -434,7 +439,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
template: `
<button :class='style'>Button</button>
`
-
+
// good
template: `
<button :class="style">Button</button>
@@ -447,7 +452,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
```javascript
// bad
props: ['foo']
-
+
// good
props: {
foo: {
@@ -467,7 +472,7 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
type: String,
}
}
-
+
// good
props: {
foo: {
@@ -490,7 +495,7 @@ On those a default key should not be provided.
required: false,
}
}
-
+
// good
props: {
foo: {
@@ -499,7 +504,7 @@ On those a default key should not be provided.
default: 'bar'
}
}
-
+
// good
props: {
foo: {
@@ -534,7 +539,7 @@ On those a default key should not be provided.
```javascript
// bad
<component v-on:click="eventHandler"/>
-
+
// good
<component @click="eventHandler"/>
```
@@ -544,7 +549,7 @@ On those a default key should not be provided.
```javascript
// bad
<component v-bind:class="btn"/>
-
+
// good
<component :class="btsn"/>
```
@@ -556,7 +561,7 @@ On those a default key should not be provided.
```javascript
// bad
<component></component>
-
+
// good
<component />
```
@@ -650,7 +655,7 @@ Useful links:
title="Some tooltip text">
Text
</span>
-
+
// good
<span
v-tooltip
@@ -666,10 +671,10 @@ Useful links:
```javascript
// bad
<span data-original-title="tooltip text">Foo</span>
-
+
// good
<span title="tooltip text">Foo</span>
-
+
$('span').tooltip('_fixTitle');
```
diff --git a/doc/development/feature_flags.md b/doc/development/feature_flags.md
index 417298205f5..0f1f079bdb4 100644
--- a/doc/development/feature_flags.md
+++ b/doc/development/feature_flags.md
@@ -69,6 +69,37 @@ For more information about rolling out changes using feature flags, refer to the
[Rolling out changes using feature flags](rolling_out_changes_using_feature_flags.md)
guide.
+### Frontend
+
+For frontend code you can use the method `push_frontend_feature_flag`, which is
+available to all controllers that inherit from `ApplicationController`. Using
+this method you can expose the state of a feature flag as follows:
+
+```ruby
+before_action do
+ push_frontend_feature_flag(:vim_bindings)
+end
+
+def index
+ # ...
+end
+
+def edit
+ # ...
+end
+```
+
+You can then check for the state of the feature flag in JavaScript as follows:
+
+```javascript
+if ( gon.features.vimBindings ) {
+ // ...
+}
+```
+
+The name of the feature flag in JavaScript will always be camelCased, meaning
+that checking for `gon.features.vim_bindings` would not work.
+
### Specs
In the test environment `Feature.enabled?` is stubbed to always respond to `true`,
diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md
index 78931defa24..bfcca9cec7b 100644
--- a/doc/development/new_fe_guide/index.md
+++ b/doc/development/new_fe_guide/index.md
@@ -19,6 +19,10 @@ Guidance on topics related to development.
Learn about all the dependencies that make up our frontend, including some of our own custom built libraries.
+## [Modules](modules/index.md)
+
+Learn about all the internal JavaScript modules that make up our frontend.
+
## [Style guides](style/index.md)
Style guides to keep our code consistent.
diff --git a/doc/development/new_fe_guide/modules/dirty_submit.md b/doc/development/new_fe_guide/modules/dirty_submit.md
new file mode 100644
index 00000000000..6c03958b463
--- /dev/null
+++ b/doc/development/new_fe_guide/modules/dirty_submit.md
@@ -0,0 +1,23 @@
+# Dirty Submit
+
+> [Introduced][ce-21115] in GitLab 11.3.
+> [dirty_submit][dirty-submit]
+
+## Summary
+
+Prevent submitting forms with no changes.
+
+Currently handles `input`, `textarea` and `select` elements.
+
+## Usage
+
+```js
+import dirtySubmitFactory from './dirty_submit/dirty_submit_form';
+
+new DirtySubmitForm(document.querySelector('form'));
+// or
+new DirtySubmitForm(document.querySelectorAll('form'));
+```
+
+[ce-21115]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21115
+[dirty-submit]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/dirty_submit/ \ No newline at end of file
diff --git a/doc/development/new_fe_guide/modules/index.md b/doc/development/new_fe_guide/modules/index.md
new file mode 100644
index 00000000000..0a7f2dbd819
--- /dev/null
+++ b/doc/development/new_fe_guide/modules/index.md
@@ -0,0 +1,5 @@
+# Modules
+
+* [DirtySubmit](dirty_submit.md)
+
+ Disable form submits until there are unsaved changes. \ No newline at end of file
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 25aa5d3369d..9e2e58657f1 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -103,7 +103,7 @@ Is the system packaged Git too old? Remove it and compile from source.
# When editing config/gitlab.yml (Step 5), change the git -> bin_path to /usr/local/bin/git
-**Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 but this [has problems](https://github.com/gitlabhq/gitlabhq/issues/4866#issuecomment-32726573) while Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with:
+**Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 but this [has problems](https://gitlab.com/gitlab-org/gitlab-ce/issues/12754) while Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with:
sudo apt-get install -y postfix
diff --git a/doc/policy/maintenance.md b/doc/policy/maintenance.md
index fc7b97b3cc2..03ba2ae8817 100644
--- a/doc/policy/maintenance.md
+++ b/doc/policy/maintenance.md
@@ -75,7 +75,8 @@ Please see the table below for some examples:
| Latest stable version | Your version | Recommended upgrade path | Note |
| -------------- | ------------ | ------------------------ | ---------------- |
| 9.4.5 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.4.5` | `8.17.7` is the last version in version `8` |
-| 10.1.4 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.5.8` -> `10.1.4` | `8.17.7` is the last version in version `8`, `9.5.8` is the last version in version `9` |
+| 10.1.4 | 8.13.4 | `8.13.4 -> 8.17.7 -> 9.5.10 -> 10.1.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9` |
+| 11.3.4 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.5.10` -> `10.8.7` -> `11.3.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9`, `10.8.7` is the last version in version `10` |
More information about the release procedures can be found in our
[release-tools documentation][rel]. You may also want to read our
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 0d1ba3e8f9a..c60d25eda1b 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -7,6 +7,14 @@ applications.
## Overview
+NOTE: **Enabled by default:**
+Starting with GitLab 11.3, the Auto DevOps pipeline will be enabled by default for all
+projects. If it's not explicitly enabled for the project, Auto DevOps will be automatically
+disabled on the first pipeline failure. Your project will continue to use an alternative
+[CI/CD configuration file](../../ci/yaml/README.md) if one is found. A GitLab
+administrator can [change this setting](../../user/admin_area/settings/continuous_integration.html#auto-devops)
+in the admin area.
+
With Auto DevOps, the software development process becomes easier to set up
as every project can have a complete workflow from verification to monitoring
without needing to configure anything. Just push your code and GitLab takes
@@ -214,22 +222,16 @@ manually triggered either by pushing a new commit to the repository or by visiti
a new pipeline for your default branch, generally `master`.
NOTE: **Note:**
-If you are a GitLab Administrator, you can enable Auto DevOps instance wide
-in **Admin Area > Settings > Continuous Integration and Deployment**. Doing that,
-all the projects that haven't explicitly set an option will have Auto DevOps
-enabled by default.
+If you are a GitLab Administrator, you can
+[enable/disable Auto DevOps instance-wide](../../user/admin_area/settings/continuous_integration.md#auto-devops),
+and all projects that haven't explicitly set an option will have Auto DevOps
+enabled/disabled by default.
NOTE: **Note:**
There is also a feature flag to enable Auto DevOps to a percentage of projects
which can be enabled from the console with
`Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(10)`.
-NOTE: **Enabled by default:**
-Starting with GitLab 11.3, the Auto DevOps pipeline will be enabled by default for all
-projects. If it's not explicitly enabled for the project, Auto DevOps will be automatically
-disabled on the first pipeline failure. Your project will continue to use an alternative
-[CI/CD configuration file](../../ci/yaml/README.md) if one is found.
-
### Deployment strategy
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/38542) in GitLab 11.0.
diff --git a/doc/update/11.3-to-11.4.md b/doc/update/11.3-to-11.4.md
index 985239369d7..b50e21f27dd 100644
--- a/doc/update/11.3-to-11.4.md
+++ b/doc/update/11.3-to-11.4.md
@@ -80,8 +80,8 @@ More information can be found on the [yarn website](https://yarnpkg.com/en/docs/
### 5. Update Go
-NOTE: GitLab 11.0 and higher only supports Go 1.9.x and newer, and dropped support for Go
-1.5.x through 1.8.x. Be sure to upgrade your installation if necessary.
+NOTE: GitLab 11.4 and higher only supports Go 1.10.x and newer, and dropped support for Go
+1.9.x. Be sure to upgrade your installation if necessary.
You can check which version you are running with `go version`.
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 1b3fb9db4ec..097b18ad496 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -281,7 +281,7 @@ Additionally locked issues can not be reopened.
[ce-14053]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14053
[ce-14061]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14061
[ce-14531]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14531
-[ce-31847]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31847
+[ce-31847]: https://gitlab.com/gitlab-org/gitlab-ce/issues/31847
[resolve-discussion-button]: img/resolve_discussion_button.png
[resolve-comment-button]: img/resolve_comment_button.png
[discussion-view]: img/discussion_view.png
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index b14377a72b6..7d01c6f2bf6 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -257,7 +257,6 @@ Learn more about [Member Lock](https://docs.gitlab.com/ee/user/group/index.html#
- **Projects**: view all projects within that group, add members to each project,
access each project's settings, and remove any project from the same screen.
- **Webhooks**: configure [webhooks](../project/integrations/webhooks.md) to your group.
-- **Push rules**: configure [push rules](https://docs.gitlab.com/ee/push_rules/push_rules.html#push-rules) to your group. **[STARTER]**
- **Audit Events**: view [Audit Events](https://docs.gitlab.com/ee/administration/audit_events.html#audit-events)
for the group. **[STARTER ONLY]**
- **Pipelines quota**: keep track of the [pipeline quota](../admin_area/settings/continuous_integration.md) for the group
diff --git a/doc/user/project/img/issue_boards_core.png b/doc/user/project/img/issue_boards_core.png
index 9e819160861..9e819160861 100755..100644
--- a/doc/user/project/img/issue_boards_core.png
+++ b/doc/user/project/img/issue_boards_core.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_premium.png b/doc/user/project/img/issue_boards_premium.png
index bd9164b2961..bd9164b2961 100755..100644
--- a/doc/user/project/img/issue_boards_premium.png
+++ b/doc/user/project/img/issue_boards_premium.png
Binary files differ
diff --git a/doc/user/project/img/koding_build-in-progress.png b/doc/user/project/img/koding_build-in-progress.png
deleted file mode 100644
index 118b97c07e1..00000000000
--- a/doc/user/project/img/koding_build-in-progress.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_build-logs.png b/doc/user/project/img/koding_build-logs.png
deleted file mode 100644
index b30c8375b20..00000000000
--- a/doc/user/project/img/koding_build-logs.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_build-success.png b/doc/user/project/img/koding_build-success.png
deleted file mode 100644
index 0f3b954abf5..00000000000
--- a/doc/user/project/img/koding_build-success.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_commit-koding.yml.png b/doc/user/project/img/koding_commit-koding.yml.png
deleted file mode 100644
index d921c73dc73..00000000000
--- a/doc/user/project/img/koding_commit-koding.yml.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_different-stack-on-mr-try.png b/doc/user/project/img/koding_different-stack-on-mr-try.png
deleted file mode 100644
index 10c7c51d2e6..00000000000
--- a/doc/user/project/img/koding_different-stack-on-mr-try.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_edit-on-ide.png b/doc/user/project/img/koding_edit-on-ide.png
deleted file mode 100644
index 25ca7694fe0..00000000000
--- a/doc/user/project/img/koding_edit-on-ide.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_enable-koding.png b/doc/user/project/img/koding_enable-koding.png
deleted file mode 100644
index 7e6c1735df2..00000000000
--- a/doc/user/project/img/koding_enable-koding.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_landing.png b/doc/user/project/img/koding_landing.png
deleted file mode 100644
index ac880376e09..00000000000
--- a/doc/user/project/img/koding_landing.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_open-gitlab-from-koding.png b/doc/user/project/img/koding_open-gitlab-from-koding.png
deleted file mode 100644
index 4235a72b36f..00000000000
--- a/doc/user/project/img/koding_open-gitlab-from-koding.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_run-in-ide.png b/doc/user/project/img/koding_run-in-ide.png
deleted file mode 100644
index fb5825a4010..00000000000
--- a/doc/user/project/img/koding_run-in-ide.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_run-mr-in-ide.png b/doc/user/project/img/koding_run-mr-in-ide.png
deleted file mode 100644
index cb1112c4034..00000000000
--- a/doc/user/project/img/koding_run-mr-in-ide.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_set-up-ide.png b/doc/user/project/img/koding_set-up-ide.png
deleted file mode 100644
index 033d41729a2..00000000000
--- a/doc/user/project/img/koding_set-up-ide.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_stack-import.png b/doc/user/project/img/koding_stack-import.png
deleted file mode 100644
index 483bfad7d6a..00000000000
--- a/doc/user/project/img/koding_stack-import.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/koding_start-build.png b/doc/user/project/img/koding_start-build.png
deleted file mode 100644
index c09a6d669f0..00000000000
--- a/doc/user/project/img/koding_start-build.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/services_templates.md b/doc/user/project/integrations/services_templates.md
index 5b04d7d88b8..a0bf31c526f 100644
--- a/doc/user/project/integrations/services_templates.md
+++ b/doc/user/project/integrations/services_templates.md
@@ -1,8 +1,10 @@
# Services templates
A GitLab administrator can add a service template that sets a default for each
-project. After a service template is enabled, it will be applied to new
-projects only and its details will be pre-filled on the project's Service page.
+project. After a service template is enabled, it will be applied to **all**
+projects that don't have it already enabled and its details will be pre-filled
+on the project's Service page. By disabling the template, it will be disabled
+for new projects only.
## Enable a service template
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 7d12cd8f7c2..7c63967c829 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -7,7 +7,7 @@
> - the `project.http_url` key is deprecated in favor of the `project.git_http_url` key
>
> **Note:**
-> Starting from GitLab 11.1, the logs of web hooks are automatically removed after
+> Starting from GitLab 11.1, the logs of webhooks are automatically removed after
> one month.
>
> **Note:**
@@ -73,8 +73,8 @@ 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
+> **Note:** When more than 20 commits are pushed at once, the `commits` webhook
+ 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.
@@ -1157,10 +1157,11 @@ its description:
```
It will appear in the webhook body as the below (assuming that GitLab is
-installed at gitlab.example.com):
+installed at gitlab.example.com, and the project is at
+example-group/example-project):
```markdown
-![image](https://gitlab.example.com/uploads/$sha/image.png)
+![image](https://gitlab.example.com/example-group/example-project/uploads/$sha/image.png)
```
This will not rewrite URLs that already are pointing to HTTP, HTTPS, or
@@ -1190,7 +1191,7 @@ From this page, you can repeat delivery with the same data by clicking `Resend R
> **Note:** If URL or secret token of the webhook were updated, data will be delivered to the new address.
-### Receiving duplicate or multiple web hook requests triggered by one event
+### Receiving duplicate or multiple webhook requests triggered by one event
When GitLab sends a webhook it expects a response in 10 seconds (set default value). If it does not receive one, it'll retry the webhook.
If the endpoint doesn't send its HTTP response within those 10 seconds, GitLab may decide the hook failed and retry it.
diff --git a/doc/user/project/koding.md b/doc/user/project/koding.md
deleted file mode 100644
index 2c886d7916a..00000000000
--- a/doc/user/project/koding.md
+++ /dev/null
@@ -1,131 +0,0 @@
-# Koding integration
-
-> **Notes:**
-> - **As of GitLab 10.0, the Koding integration is deprecated and will be removed
-> in a future version.**
-> - [Introduced][ce-5909] in GitLab 8.11.
-
-This document will guide you through using Koding integration on GitLab in
-detail. For configuring and installing please follow the
-[administrator guide](../../administration/integration/koding.md).
-
-You can use Koding integration to run and develop your projects on GitLab. This
-will allow you and the users to test your project without leaving the browser.
-Koding handles projects as stacks which are basic recipes to define your
-environment for your project. With this integration you can automatically
-create a proper stack template for your projects. Currently auto-generated
-stack templates are designed to work with AWS which requires a valid AWS
-credential to be able to use these stacks. You can find more information about
-stacks and the other providers that you can use on Koding following the
-[Koding documentation][koding-docs].
-
-## Enable Integration
-
-You can enable Koding integration by providing the running Koding instance URL
-in Application Settings under **Admin area > Settings** (`/admin/application_settings`).
-
-![Enable Koding](img/koding_enable-koding.png)
-
-Once enabled you will see `Koding` link on your sidebar which leads you to
-Koding Landing page.
-
-![Koding Landing](img/koding_landing.png)
-
-You can navigate to running Koding instance from here. For more information and
-details about configuring the integration, please follow the
-[administrator guide](../../administration/integration/koding.md).
-
-## Set up Koding on Projects
-
-Once it's enabled, you will see some integration buttons on Project pages,
-Merge Requests etc. To get started working on a specific project you first need
-to create a `.koding.yml` file under your project root. You can easily do that
-by using `Set Up Koding` button which will be visible on every project's
-landing page;
-
-![Set Up Koding](img/koding_set-up-ide.png)
-
-Once you click this will open a New File page on GitLab with auto-generated
-`.koding.yml` content based on your server and repository configuration.
-
-![Commit .koding.yml](img/koding_commit-koding.yml.png)
-
-
-## Run a project on Koding
-
-If there is `.koding.yml` exists in your project root, you will see
-`Run in IDE (Koding)` button in your project landing page. You can initiate the
-process from here.
-
-![Run on Koding](img/koding_run-in-ide.png)
-
-This will open Koding defined in the settings in a new window and will start
-importing the project's stack file.
-
-![Import Stack](img/koding_stack-import.png)
-
-You should see the details of your repository imported into your Koding
-instance. Once it's completed it will lead you to the Stack Editor and from
-there you can start using your new stack integrated with your project on your
-GitLab instance. For details about what's next you can follow
-[this guide](https://www.koding.com/docs/creating-an-aws-stack) from step 8.
-
-Once stack initialized you will see the `README.md` content from your project
-in `Stack Build` wizard, this wizard will let you build the stack and import
-your project into it. **Once it's completed it will automatically open the
-related vm instead of importing from scratch**.
-
-![Stack Building](img/koding_start-build.png)
-
-This will take time depending on the required environment.
-
-![Stack Building in Progress](img/koding_build-in-progress.png)
-
-It usually takes ~4 min. to make it ready with a `t2.nano` instance on given
-AWS region. (`t2.nano` is default vm type on auto-generated stack template
-which can be manually changed).
-
-![Stack Building Success](img/koding_build-success.png)
-
-You can check out the `Build Logs` from this success modal as well.
-
-![Stack Build Logs](img/koding_build-logs.png)
-
-You can now `Start Coding`!
-
-![Edit On IDE](img/koding_edit-on-ide.png)
-
-## Try a Merge Request on IDE
-
-It's also possible to try a change on IDE before merging it. This flow only
-enabled if the target project has `.koding.yml` in it's target branch. You
-should see the alternative version of `Run in IDE (Koding)` button in merge
-request pages as well;
-
-![Run in IDE on MR](img/koding_run-mr-in-ide.png)
-
-This will again take you to Koding with proper arguments passed, which will
-allow Koding to modify the stack template provided by target branch. You can
-see the difference;
-
-![Different Branch for MR](img/koding_different-stack-on-mr-try.png)
-
-The flow for the branch stack is also same with the regular project flow.
-
-## Open GitLab from Koding
-
-Since stacks generated with import flow defined in previous steps, they have
-information about the repository they are belonging to. By using this
-information you can access to related GitLab page from stacks on your sidebar
-on Koding.
-
-![Open GitLab from Koding](img/koding_open-gitlab-from-koding.png)
-
-## Other links
-
-- [YouTube video on GitLab + Koding workflow][youtube]
-- [Koding documentation][koding-docs]
-
-[ce-5909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5909
-[youtube]: https://youtu.be/3wei5yv_Ye8
-[koding-docs]: https://www.koding.com/docs
diff --git a/doc/user/project/merge_requests/cherry_pick_changes.md b/doc/user/project/merge_requests/cherry_pick_changes.md
index 22ef11e4049..06b3779668b 100644
--- a/doc/user/project/merge_requests/cherry_pick_changes.md
+++ b/doc/user/project/merge_requests/cherry_pick_changes.md
@@ -12,9 +12,11 @@ to cherry-pick the changes introduced by that merge request.
![Cherry-pick Merge Request](img/cherry_pick_changes_mr.png)
-After you click that button, a modal will appear where you can choose to
-cherry-pick the changes directly into the selected branch or you can opt to
-create a new merge request with the cherry-pick changes
+After you click that button, a modal will appear showing a [branch filter search box](../repository/branches/index.md#branch-filter-search-box)
+where you can choose to either:
+
+- Cherry-pick the changes directly into the selected branch.
+- Create a new merge request with the cherry-picked changes.
## Cherry-picking a Commit
diff --git a/doc/user/project/merge_requests/img/merge_request_diff_file_navigation.png b/doc/user/project/merge_requests/img/merge_request_diff_file_navigation.png
index ac766c99935..3b3bf88df31 100644
--- a/doc/user/project/merge_requests/img/merge_request_diff_file_navigation.png
+++ b/doc/user/project/merge_requests/img/merge_request_diff_file_navigation.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 43ca498d006..f9ebf277125 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -205,9 +205,9 @@ have been marked as a **Work In Progress**.
## Merge request diff file navigation
-The diff view has a persistent dropdown for file navigation. As you scroll through
-diffs with a large number of files and/or many changes in those files, you can
-easily jump to any changed file through the dropdown navigation.
+The diff view has a file tree for file navigation. As you scroll through
+diffs with a large number of files, you can easily jump to any changed file
+using the file tree.
![Merge request diff file navigation](img/merge_request_diff_file_navigation.png)
diff --git a/doc/user/project/repository/branches/img/branch_filter_search_box.png b/doc/user/project/repository/branches/img/branch_filter_search_box.png
new file mode 100644
index 00000000000..c4364ef39f4
--- /dev/null
+++ b/doc/user/project/repository/branches/img/branch_filter_search_box.png
Binary files differ
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index 19417d91fec..e1d8345f415 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -6,6 +6,7 @@ Read through GiLab's branching documentation:
- [Default branch](#default-branch)
- [Protected branches](../../protected_branches.md#protected-branches)
- [Delete merged branches](#delete-merged-branches)
+- [Branch filter search box](#branch-filter-search-box)
See also:
@@ -40,5 +41,22 @@ this operation.
It's particularly useful to clean up old branches that were not deleted
automatically when a merge request was merged.
+
+## Branch filter search box
+
+> [Introduced][https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22166] in GitLab 11.5.
+
+![Branch filter search box](img/branch_filter_search_box.png)
+
+This feature allows you to search and select branches quickly. Search results appear in the following order:
+
+- Branches with names that matched search terms exactly.
+- Other branches with names that include search terms, sorted alphabetically.
+
+Sometimes when you have hundreds of branches you may want a more flexible matching pattern. In such cases you can use the following:
+
+- `^feature` will only match branch names that begin with 'feature'.
+- `feature$` will only match branch names that end with 'feature'.
+
[ce-6449]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449 "Add button to delete all merged branches"
[protected]: ../../protected_branches.md
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 4d016277824..beff4b89424 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -85,12 +85,13 @@ You can live preview changes submitted to a new branch with
With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request
[approval](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html) from your managers.
-To create, delete, and [branches](branches/index.md) via GitLab's UI:
+To create, delete, and view [branches](branches/index.md) via GitLab's UI:
- [Default branches](branches/index.md#default-branch)
- [Create a branch](web_editor.md#create-a-new-branch)
- [Protected branches](../protected_branches.md#protected-branches)
- [Delete merged branches](branches/index.md#delete-merged-branches)
+- [Branch filter search box](branches/index.md#branch-filter-search-box)
Alternatively, you can use the
[command line](../../../gitlab-basics/start-using-git.md#create-a-branch).
@@ -165,11 +166,15 @@ minutes.
![Repository Languages bar](img/repository_languages.png)
Not all files are detected, among others; documentation,
-vendored code, and most markup languages are excluded.
+vendored code, and most markup languages are excluded. This behaviour can be
+adjusted by overriding the default. For example, to enable `.proto` files to be
+detected, add the following to `.gitattributes` in the root of your repository.
+
+> *.proto linguist-detectable=true
## Compare
-Select branches to compare and view the changes inline:
+Select branches to compare using the [branch filter search box](branches/index.md#branch-filter-search-box), then click the **Compare** button to view the changes inline:
![compare branches](img/compare_branches.png)
diff --git a/doc/user/reserved_names.md b/doc/user/reserved_names.md
index 52610378ad5..45c306f5988 100644
--- a/doc/user/reserved_names.md
+++ b/doc/user/reserved_names.md
@@ -68,7 +68,6 @@ Currently the following names are reserved as top level groups:
- import
- invites
- jwt
-- koding
- notification_settings
- oauth
- profile
diff --git a/lib/api/circuit_breakers.rb b/lib/api/circuit_breakers.rb
index 6eddc5e5b61..da756daadcc 100644
--- a/lib/api/circuit_breakers.rb
+++ b/lib/api/circuit_breakers.rb
@@ -13,37 +13,24 @@ module API
end
resource ':type' do
namespace '', requirements: { type: 'repository_storage' } do
- helpers do
- def failing_storage_health
- @failing_storage_health ||= Gitlab::Git::Storage::Health.for_failing_storages
- end
-
- def storage_health
- @storage_health ||= Gitlab::Git::Storage::Health.for_all_storages
- end
- end
-
desc 'Get all git storages' do
detail 'This feature was introduced in GitLab 9.5'
- success Entities::RepositoryStorageHealth
end
get do
- present storage_health, with: Entities::RepositoryStorageHealth
+ present []
end
desc 'Get all failing git storages' do
detail 'This feature was introduced in GitLab 9.5'
- success Entities::RepositoryStorageHealth
end
get 'failing' do
- present failing_storage_health, with: Entities::RepositoryStorageHealth
+ present []
end
desc 'Reset all storage failures and open circuitbreaker' do
detail 'This feature was introduced in GitLab 9.5'
end
delete do
- Gitlab::Git::Storage::FailureInfo.reset_all!
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 120545792f2..5a4b85f98cf 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1364,12 +1364,6 @@ module API
expose :submitted, as: :akismet_submitted
end
- class RepositoryStorageHealth < Grape::Entity
- expose :storage_name
- expose :failing_on_hosts
- expose :total_failures
- end
-
class CustomAttribute < Grape::Entity
expose :key
expose :value
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 5125f302fbb..dc844c0bd27 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -130,18 +130,13 @@ module API
success Entities::Commit
end
params do
- # For now we just support 2 refs passed, but `merge-base` supports
- # multiple defining this as an Array instead of 2 separate params will
- # make sure we don't need to deprecate this API in favor of one
- # supporting multiple commits when this functionality gets added to
- # Gitaly
requires :refs, type: Array[String]
end
get ':id/repository/merge_base' do
refs = params[:refs]
- unless refs.size == 2
- render_api_error!('Provide exactly 2 refs', 400)
+ if refs.size < 2
+ render_api_error!('Provide at least 2 refs', 400)
end
merge_base = Gitlab::Git::MergeBase.new(user_project.repository, refs)
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index edbd134822c..f53ba0ab761 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -66,10 +66,6 @@ module API
optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.'
optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project manifest],
desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com'
- optional :koding_enabled, type: Boolean, desc: 'Enable Koding'
- given koding_enabled: ->(val) { val } do
- requires :koding_url, type: String, desc: 'The Koding team URL'
- end
optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size for each job's artifacts"
optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB'
optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB'
diff --git a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
index 91175b49c79..15cdd25e711 100644
--- a/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
+++ b/lib/generators/rails/post_deployment_migration/post_deployment_migration_generator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails/generators'
module Rails
diff --git a/lib/gitaly/server.rb b/lib/gitaly/server.rb
index f95e423ef22..7b238623418 100644
--- a/lib/gitaly/server.rb
+++ b/lib/gitaly/server.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitaly
class Server
def self.all
diff --git a/lib/gitlab/auth/activity.rb b/lib/gitlab/auth/activity.rb
index 761f0819c60..558628b5422 100644
--- a/lib/gitlab/auth/activity.rb
+++ b/lib/gitlab/auth/activity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
##
diff --git a/lib/gitlab/auth/database/authentication.rb b/lib/gitlab/auth/database/authentication.rb
index 1234ace0334..c0dc2b0875f 100644
--- a/lib/gitlab/auth/database/authentication.rb
+++ b/lib/gitlab/auth/database/authentication.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These calls help to authenticate to OAuth provider by providing username and password
#
diff --git a/lib/gitlab/auth/ip_rate_limiter.rb b/lib/gitlab/auth/ip_rate_limiter.rb
index e6173d45af3..81e616fa20a 100644
--- a/lib/gitlab/auth/ip_rate_limiter.rb
+++ b/lib/gitlab/auth/ip_rate_limiter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
class IpRateLimiter
diff --git a/lib/gitlab/auth/ldap/access.rb b/lib/gitlab/auth/ldap/access.rb
index f323d2e0f7a..c875bba4bcb 100644
--- a/lib/gitlab/auth/ldap/access.rb
+++ b/lib/gitlab/auth/ldap/access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# LDAP authorization model
#
# * Check if we are allowed access (not blocked)
diff --git a/lib/gitlab/auth/ldap/adapter.rb b/lib/gitlab/auth/ldap/adapter.rb
index 82ff1e77e5c..42c657afe6a 100644
--- a/lib/gitlab/auth/ldap/adapter.rb
+++ b/lib/gitlab/auth/ldap/adapter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
module LDAP
diff --git a/lib/gitlab/auth/ldap/auth_hash.rb b/lib/gitlab/auth/ldap/auth_hash.rb
index ac5c14d374d..83fdc8a8c76 100644
--- a/lib/gitlab/auth/ldap/auth_hash.rb
+++ b/lib/gitlab/auth/ldap/auth_hash.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Class to parse and transform the info provided by omniauth
#
module Gitlab
diff --git a/lib/gitlab/auth/ldap/authentication.rb b/lib/gitlab/auth/ldap/authentication.rb
index 7c134fb6438..174e81dd603 100644
--- a/lib/gitlab/auth/ldap/authentication.rb
+++ b/lib/gitlab/auth/ldap/authentication.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These calls help to authenticate to LDAP by providing username and password
#
# Since multiple LDAP servers are supported, it will loop through all of them
diff --git a/lib/gitlab/auth/ldap/config.rb b/lib/gitlab/auth/ldap/config.rb
index d4415eaa6dc..7ceb96f502b 100644
--- a/lib/gitlab/auth/ldap/config.rb
+++ b/lib/gitlab/auth/ldap/config.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Load a specific server configuration
module Gitlab
module Auth
diff --git a/lib/gitlab/auth/ldap/dn.rb b/lib/gitlab/auth/ldap/dn.rb
index 1fa5338f5a6..5df914aa367 100644
--- a/lib/gitlab/auth/ldap/dn.rb
+++ b/lib/gitlab/auth/ldap/dn.rb
@@ -1,4 +1,5 @@
# -*- ruby encoding: utf-8 -*-
+# frozen_string_literal: true
# Based on the `ruby-net-ldap` gem's `Net::LDAP::DN`
#
diff --git a/lib/gitlab/auth/ldap/ldap_connection_error.rb b/lib/gitlab/auth/ldap/ldap_connection_error.rb
index ef0a695742b..d0e5f24d203 100644
--- a/lib/gitlab/auth/ldap/ldap_connection_error.rb
+++ b/lib/gitlab/auth/ldap/ldap_connection_error.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
module LDAP
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index 8dfae3ee541..a0244a3cea1 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
module LDAP
diff --git a/lib/gitlab/auth/ldap/user.rb b/lib/gitlab/auth/ldap/user.rb
index 3c21ddf3241..9c71671f409 100644
--- a/lib/gitlab/auth/ldap/user.rb
+++ b/lib/gitlab/auth/ldap/user.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# LDAP extension for User model
#
# * Find or create user from omniauth.auth data
diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb
index ed8fba94305..4a5f9d2839d 100644
--- a/lib/gitlab/auth/o_auth/auth_hash.rb
+++ b/lib/gitlab/auth/o_auth/auth_hash.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Class to parse and transform the info provided by omniauth
#
module Gitlab
diff --git a/lib/gitlab/auth/o_auth/authentication.rb b/lib/gitlab/auth/o_auth/authentication.rb
index d4e7f35c857..5f008678bd1 100644
--- a/lib/gitlab/auth/o_auth/authentication.rb
+++ b/lib/gitlab/auth/o_auth/authentication.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These calls help to authenticate to OAuth provider by providing username and password
#
diff --git a/lib/gitlab/auth/o_auth/identity_linker.rb b/lib/gitlab/auth/o_auth/identity_linker.rb
index de92d7a214d..e69c2bb54dc 100644
--- a/lib/gitlab/auth/o_auth/identity_linker.rb
+++ b/lib/gitlab/auth/o_auth/identity_linker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
module OAuth
diff --git a/lib/gitlab/auth/o_auth/provider.rb b/lib/gitlab/auth/o_auth/provider.rb
index 26da9d09ccc..9fdf3324db3 100644
--- a/lib/gitlab/auth/o_auth/provider.rb
+++ b/lib/gitlab/auth/o_auth/provider.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
module OAuth
diff --git a/lib/gitlab/auth/o_auth/session.rb b/lib/gitlab/auth/o_auth/session.rb
index 8f2b4d58552..4925b107042 100644
--- a/lib/gitlab/auth/o_auth/session.rb
+++ b/lib/gitlab/auth/o_auth/session.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# :nocov:
module Gitlab
module Auth
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index 2b4f6ed75e5..a4e8a41b246 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# OAuth extension for User model
#
# * Find GitLab user based on omniauth uid and provider
diff --git a/lib/gitlab/auth/omniauth_identity_linker_base.rb b/lib/gitlab/auth/omniauth_identity_linker_base.rb
index 8ae29a02a13..253445570f2 100644
--- a/lib/gitlab/auth/omniauth_identity_linker_base.rb
+++ b/lib/gitlab/auth/omniauth_identity_linker_base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
class OmniauthIdentityLinkerBase
diff --git a/lib/gitlab/auth/request_authenticator.rb b/lib/gitlab/auth/request_authenticator.rb
index 66de52506ce..cb9f2582936 100644
--- a/lib/gitlab/auth/request_authenticator.rb
+++ b/lib/gitlab/auth/request_authenticator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Use for authentication only, in particular for Rack::Attack.
# Does not perform authorization of scopes, etc.
module Gitlab
diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb
index 00cdc94a9ef..78fa25c5516 100644
--- a/lib/gitlab/auth/result.rb
+++ b/lib/gitlab/auth/result.rb
@@ -1,4 +1,7 @@
-module Gitlab # rubocop:disable Naming/FileName
+# rubocop:disable Naming/FileName
+# frozen_string_literal: true
+
+module Gitlab
module Auth
Result = Struct.new(:actor, :project, :type, :authentication_abilities) do
def ci?(for_project)
diff --git a/lib/gitlab/auth/saml/auth_hash.rb b/lib/gitlab/auth/saml/auth_hash.rb
index 3bc5e2864df..316354fd50c 100644
--- a/lib/gitlab/auth/saml/auth_hash.rb
+++ b/lib/gitlab/auth/saml/auth_hash.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
module Saml
diff --git a/lib/gitlab/auth/saml/config.rb b/lib/gitlab/auth/saml/config.rb
index 625dab7c6f4..8cb999f50d4 100644
--- a/lib/gitlab/auth/saml/config.rb
+++ b/lib/gitlab/auth/saml/config.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
module Saml
diff --git a/lib/gitlab/auth/saml/identity_linker.rb b/lib/gitlab/auth/saml/identity_linker.rb
index 7e4b191d512..ae0d6dded4e 100644
--- a/lib/gitlab/auth/saml/identity_linker.rb
+++ b/lib/gitlab/auth/saml/identity_linker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
module Saml
diff --git a/lib/gitlab/auth/saml/user.rb b/lib/gitlab/auth/saml/user.rb
index 6c3b75f3eb0..ec95bc46791 100644
--- a/lib/gitlab/auth/saml/user.rb
+++ b/lib/gitlab/auth/saml/user.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# SAML extension for User model
#
# * Find GitLab user based on SAML uid and provider
diff --git a/lib/gitlab/auth/too_many_ips.rb b/lib/gitlab/auth/too_many_ips.rb
index ed862791551..ee4d80e6b89 100644
--- a/lib/gitlab/auth/too_many_ips.rb
+++ b/lib/gitlab/auth/too_many_ips.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
class TooManyIps < StandardError
diff --git a/lib/gitlab/auth/unique_ips_limiter.rb b/lib/gitlab/auth/unique_ips_limiter.rb
index baa1f802d8a..31dd61ae6cf 100644
--- a/lib/gitlab/auth/unique_ips_limiter.rb
+++ b/lib/gitlab/auth/unique_ips_limiter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
class UniqueIpsLimiter
diff --git a/lib/gitlab/auth/user_access_denied_reason.rb b/lib/gitlab/auth/user_access_denied_reason.rb
index 1893cb001b2..fd09fe76c02 100644
--- a/lib/gitlab/auth/user_access_denied_reason.rb
+++ b/lib/gitlab/auth/user_access_denied_reason.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
class UserAccessDeniedReason
diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb
index 064cba43278..5df6db6f366 100644
--- a/lib/gitlab/auth/user_auth_finders.rb
+++ b/lib/gitlab/auth/user_auth_finders.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Auth
AuthenticationError = Class.new(StandardError)
diff --git a/lib/gitlab/badge/base.rb b/lib/gitlab/badge/base.rb
index 909fa24fa90..fb55b9e2f1f 100644
--- a/lib/gitlab/badge/base.rb
+++ b/lib/gitlab/badge/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Badge
class Base
diff --git a/lib/gitlab/badge/coverage/metadata.rb b/lib/gitlab/badge/coverage/metadata.rb
index e898f5d790e..9181ba2d4b0 100644
--- a/lib/gitlab/badge/coverage/metadata.rb
+++ b/lib/gitlab/badge/coverage/metadata.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Badge
module Coverage
diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb
index 16fd6f01495..a7fcb6b0fca 100644
--- a/lib/gitlab/badge/coverage/report.rb
+++ b/lib/gitlab/badge/coverage/report.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Badge
module Coverage
diff --git a/lib/gitlab/badge/coverage/template.rb b/lib/gitlab/badge/coverage/template.rb
index afbf9dd17e3..817dc28f84a 100644
--- a/lib/gitlab/badge/coverage/template.rb
+++ b/lib/gitlab/badge/coverage/template.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Badge
module Coverage
diff --git a/lib/gitlab/badge/metadata.rb b/lib/gitlab/badge/metadata.rb
index 8ad6f3cb986..b9ae68134b0 100644
--- a/lib/gitlab/badge/metadata.rb
+++ b/lib/gitlab/badge/metadata.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Badge
##
diff --git a/lib/gitlab/badge/pipeline/metadata.rb b/lib/gitlab/badge/pipeline/metadata.rb
index db1e9f8cfb8..d4d789558c9 100644
--- a/lib/gitlab/badge/pipeline/metadata.rb
+++ b/lib/gitlab/badge/pipeline/metadata.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Badge
module Pipeline
diff --git a/lib/gitlab/badge/pipeline/status.rb b/lib/gitlab/badge/pipeline/status.rb
index d1d9b7949f5..37e61f07e5b 100644
--- a/lib/gitlab/badge/pipeline/status.rb
+++ b/lib/gitlab/badge/pipeline/status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Badge
module Pipeline
diff --git a/lib/gitlab/badge/pipeline/template.rb b/lib/gitlab/badge/pipeline/template.rb
index e09db32262d..64c3dfcd10b 100644
--- a/lib/gitlab/badge/pipeline/template.rb
+++ b/lib/gitlab/badge/pipeline/template.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Badge
module Pipeline
diff --git a/lib/gitlab/badge/template.rb b/lib/gitlab/badge/template.rb
index bfeb0052642..ed2ec50b197 100644
--- a/lib/gitlab/badge/template.rb
+++ b/lib/gitlab/badge/template.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Badge
##
diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb
index 04aa6aab771..3cd327f5109 100644
--- a/lib/gitlab/bare_repository_import/importer.rb
+++ b/lib/gitlab/bare_repository_import/importer.rb
@@ -1,10 +1,15 @@
+# frozen_string_literal: true
+
module Gitlab
module BareRepositoryImport
class Importer
NoAdminError = Class.new(StandardError)
def self.execute(import_path)
- import_path << '/' unless import_path.ends_with?('/')
+ unless import_path.ends_with?('/')
+ import_path = "#{import_path}/"
+ end
+
repos_to_import = Dir.glob(import_path + '**/*.git')
unless user = User.admins.order_id_asc.first
diff --git a/lib/gitlab/bare_repository_import/repository.rb b/lib/gitlab/bare_repository_import/repository.rb
index c0c666dfb7b..b903c581aac 100644
--- a/lib/gitlab/bare_repository_import/repository.rb
+++ b/lib/gitlab/bare_repository_import/repository.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module BareRepositoryImport
class Repository
@@ -6,9 +8,12 @@ module Gitlab
attr_reader :group_path, :project_name, :repo_path
def initialize(root_path, repo_path)
+ unless root_path.ends_with?('/')
+ root_path = "#{root_path}/"
+ end
+
@root_path = root_path
@repo_path = repo_path
- @root_path << '/' unless root_path.ends_with?('/')
full_path =
if hashed? && !wiki?
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index a7dfccea2f6..45e550b3450 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module BitbucketImport
class Importer
diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb
index d94f70fd1fb..11070a68e02 100644
--- a/lib/gitlab/bitbucket_import/project_creator.rb
+++ b/lib/gitlab/bitbucket_import/project_creator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module BitbucketImport
class ProjectCreator
diff --git a/lib/gitlab/bitbucket_server_import/project_creator.rb b/lib/gitlab/bitbucket_server_import/project_creator.rb
index 35e8cd7e0ab..48ca4951957 100644
--- a/lib/gitlab/bitbucket_server_import/project_creator.rb
+++ b/lib/gitlab/bitbucket_server_import/project_creator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module BitbucketServerImport
class ProjectCreator
diff --git a/lib/gitlab/blob_helper.rb b/lib/gitlab/blob_helper.rb
new file mode 100644
index 00000000000..9b3b383b0c8
--- /dev/null
+++ b/lib/gitlab/blob_helper.rb
@@ -0,0 +1,145 @@
+# This has been extracted from https://github.com/github/linguist/blob/master/lib/linguist/blob_helper.rb
+module Gitlab
+ module BlobHelper
+ def extname
+ File.extname(name.to_s)
+ end
+
+ def known_extension?
+ LanguageData.extensions.include?(extname)
+ end
+
+ def viewable?
+ !large? && text?
+ end
+
+ MEGABYTE = 1024 * 1024
+
+ def large?
+ size.to_i > MEGABYTE
+ end
+
+ def binary?
+ # Large blobs aren't even loaded into memory
+ if data.nil?
+ true
+
+ # Treat blank files as text
+ elsif data == ""
+ false
+
+ # Charlock doesn't know what to think
+ elsif encoding.nil?
+ true
+
+ # If Charlock says its binary
+ else
+ detect_encoding[:type] == :binary
+ end
+ end
+
+ def text?
+ !binary?
+ end
+
+ def image?
+ ['.png', '.jpg', '.jpeg', '.gif'].include?(extname.downcase)
+ end
+
+ # Internal: Lookup mime type for extension.
+ #
+ # Returns a MIME::Type
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def _mime_type
+ if defined? @_mime_type
+ @_mime_type
+ else
+ guesses = ::MIME::Types.type_for(extname.to_s)
+
+ # Prefer text mime types over binary
+ @_mime_type = guesses.detect { |type| type.ascii? } || guesses.first
+ end
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ # Public: Get the actual blob mime type
+ #
+ # Examples
+ #
+ # # => 'text/plain'
+ # # => 'text/html'
+ #
+ # Returns a mime type String.
+ def mime_type
+ _mime_type ? _mime_type.to_s : 'text/plain'
+ end
+
+ def binary_mime_type?
+ _mime_type ? _mime_type.binary? : false
+ end
+
+ def lines
+ @lines ||=
+ if viewable? && data
+ # `data` is usually encoded as ASCII-8BIT even when the content has
+ # been detected as a different encoding. However, we are not allowed
+ # to change the encoding of `data` because we've made the implicit
+ # guarantee that each entry in `lines` is encoded the same way as
+ # `data`.
+ #
+ # Instead, we re-encode each possible newline sequence as the
+ # detected encoding, then force them back to the encoding of `data`
+ # (usually a binary encoding like ASCII-8BIT). This means that the
+ # byte sequence will match how newlines are likely encoded in the
+ # file, but we don't have to change the encoding of `data` as far as
+ # Ruby is concerned. This allows us to correctly parse out each line
+ # without changing the encoding of `data`, and
+ # also--importantly--without having to duplicate many (potentially
+ # large) strings.
+ begin
+ data.split(encoded_newlines_re, -1)
+ rescue Encoding::ConverterNotFoundError
+ # The data is not splittable in the detected encoding. Assume it's
+ # one big line.
+ [data]
+ end
+ else
+ []
+ end
+ end
+
+ def content_type
+ # rubocop:disable Style/MultilineTernaryOperator
+ # rubocop:disable Style/NestedTernaryOperator
+ @content_type ||= binary_mime_type? || binary? ? mime_type :
+ (encoding ? "text/plain; charset=#{encoding.downcase}" : "text/plain")
+ # rubocop:enable Style/NestedTernaryOperator
+ # rubocop:enable Style/MultilineTernaryOperator
+ end
+
+ def encoded_newlines_re
+ @encoded_newlines_re ||=
+ Regexp.union(["\r\n", "\r", "\n"].map { |nl| nl.encode(ruby_encoding, "ASCII-8BIT").force_encoding(data.encoding) })
+ end
+
+ def ruby_encoding
+ if hash = detect_encoding
+ hash[:ruby_encoding]
+ end
+ end
+
+ def encoding
+ if hash = detect_encoding
+ hash[:encoding]
+ end
+ end
+
+ def detect_encoding
+ @detect_encoding ||= CharlockHolmes::EncodingDetector.new.detect(data) if data # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ end
+
+ def empty?
+ data.nil? || data == ""
+ end
+ end
+end
diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb
index add048d671e..b369b9e7600 100644
--- a/lib/gitlab/cache/ci/project_pipeline_status.rb
+++ b/lib/gitlab/cache/ci/project_pipeline_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class is not backed by a table in the main database.
# It loads the latest Pipeline for the HEAD of a repository, and caches that
# in Redis.
diff --git a/lib/gitlab/cache/request_cache.rb b/lib/gitlab/cache/request_cache.rb
index b96e161a5b6..4c658dc0b8d 100644
--- a/lib/gitlab/cache/request_cache.rb
+++ b/lib/gitlab/cache/request_cache.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Cache
# See https://docs.gitlab.com/ee/development/utilities.html#requestcache
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index 7a4224e5bbe..49e7f7e1fd7 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Checks
class ChangeAccess
diff --git a/lib/gitlab/checks/commit_check.rb b/lib/gitlab/checks/commit_check.rb
index 7e0c34aada3..6dd74e8fb74 100644
--- a/lib/gitlab/checks/commit_check.rb
+++ b/lib/gitlab/checks/commit_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Checks
class CommitCheck
diff --git a/lib/gitlab/checks/force_push.rb b/lib/gitlab/checks/force_push.rb
index 87af4a90572..263972923ed 100644
--- a/lib/gitlab/checks/force_push.rb
+++ b/lib/gitlab/checks/force_push.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Checks
class ForcePush
diff --git a/lib/gitlab/checks/lfs_integrity.rb b/lib/gitlab/checks/lfs_integrity.rb
index 3f7adecc621..fa3dc1808df 100644
--- a/lib/gitlab/checks/lfs_integrity.rb
+++ b/lib/gitlab/checks/lfs_integrity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Checks
class LfsIntegrity
diff --git a/lib/gitlab/checks/matching_merge_request.rb b/lib/gitlab/checks/matching_merge_request.rb
index 86f4aaeb4d3..71361b12d07 100644
--- a/lib/gitlab/checks/matching_merge_request.rb
+++ b/lib/gitlab/checks/matching_merge_request.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Checks
class MatchingMergeRequest
diff --git a/lib/gitlab/checks/post_push_message.rb b/lib/gitlab/checks/post_push_message.rb
index 473c0385b34..492dbb5a596 100644
--- a/lib/gitlab/checks/post_push_message.rb
+++ b/lib/gitlab/checks/post_push_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Checks
class PostPushMessage
diff --git a/lib/gitlab/checks/project_created.rb b/lib/gitlab/checks/project_created.rb
index cec270d6a58..0058a402a62 100644
--- a/lib/gitlab/checks/project_created.rb
+++ b/lib/gitlab/checks/project_created.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Checks
class ProjectCreated < PostPushMessage
diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb
index 3a197078d08..cb3b7acaaad 100644
--- a/lib/gitlab/checks/project_moved.rb
+++ b/lib/gitlab/checks/project_moved.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Gitlab
module Checks
class ProjectMoved < PostPushMessage
diff --git a/lib/gitlab/ci/templates/Android.gitlab-ci.yml b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
index 5f9d54ff574..bf7831b937c 100644
--- a/lib/gitlab/ci/templates/Android.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
@@ -2,33 +2,33 @@
image: openjdk:8-jdk
variables:
- ANDROID_COMPILE_SDK: "25"
- ANDROID_BUILD_TOOLS: "24.0.0"
- ANDROID_SDK_TOOLS: "24.4.1"
+ ANDROID_COMPILE_SDK: "28"
+ ANDROID_BUILD_TOOLS: "28.0.3"
+ ANDROID_SDK_TOOLS: "26.1.1"
before_script:
- - apt-get --quiet update --yes
- - apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
- - wget --quiet --output-document=android-sdk.tgz https://dl.google.com/android/android-sdk_r${ANDROID_SDK_TOOLS}-linux.tgz
- - tar --extract --gzip --file=android-sdk.tgz
- - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter android-${ANDROID_COMPILE_SDK}
- - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter platform-tools
- - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter build-tools-${ANDROID_BUILD_TOOLS}
- - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-android-m2repository
- - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-google_play_services
- - echo y | android-sdk-linux/tools/android --silent update sdk --no-ui --all --filter extra-google-m2repository
- - export ANDROID_HOME=$PWD/android-sdk-linux
- - export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/
- - chmod +x ./gradlew
+- apt-get --quiet update --yes
+- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1
+- wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip
+- unzip android-sdk.zip -d android-sdk-linux
+- echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" > /dev/null
+- echo y | android-sdk-linux/tools/bin/sdkmanager platform-tools > /dev/null
+- echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" > /dev/null
+- echo y | android-sdk-linux/tools/bin/sdkmanager "extras;google;google_play_services" > /dev/null
+- echo y | android-sdk-linux/tools/bin/sdkmanager "extras;google;m2repository" > /dev/null
+- export ANDROID_HOME=$PWD/android-sdk-linux
+- export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/
+- yes | android-sdk-linux/tools/bin/sdkmanager --licenses &
+- chmod +x ./gradlew
stages:
- - build
- - test
+- build
+- test
build:
stage: build
script:
- - ./gradlew assembleDebug
+ - ./gradlew assembleDebug
artifacts:
paths:
- app/build/outputs/
@@ -36,7 +36,7 @@ build:
unitTests:
stage: test
script:
- - ./gradlew test
+ - ./gradlew test
functionalTests:
stage: test
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 72547c1b407..6fa59e41d20 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -126,8 +126,8 @@ license_management:
artifacts:
paths: [gl-license-management-report.json]
only:
- - branches
- only:
+ refs:
+ - branches
variables:
- $GITLAB_FEATURES =~ /\blicense_management\b/
except:
diff --git a/lib/gitlab/conflict/file.rb b/lib/gitlab/conflict/file.rb
index 2a0cb640a14..30911b49b18 100644
--- a/lib/gitlab/conflict/file.rb
+++ b/lib/gitlab/conflict/file.rb
@@ -158,7 +158,6 @@ module Gitlab
json_hash.tap do |json_hash|
if opts[:full_content]
json_hash[:content] = content
- json_hash[:blob_ace_mode] = our_blob && our_blob.language.try(:ace_mode)
else
json_hash[:sections] = sections if type.text?
json_hash[:type] = type
diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb
index 8f55e94975c..da62ed2fb16 100644
--- a/lib/gitlab/file_detector.rb
+++ b/lib/gitlab/file_detector.rb
@@ -18,7 +18,6 @@ module Gitlab
# Configuration files
gitignore: '.gitignore',
- koding: '.koding.yml',
gitlab_ci: '.gitlab-ci.yml',
route_map: '.gitlab/route-map.yml',
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index 71857bd2d87..13b0bb930f4 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -3,13 +3,13 @@
module Gitlab
module Git
class Blob
- include Linguist::BlobHelper
+ include Gitlab::BlobHelper
include Gitlab::EncodingHelper
# This number is the maximum amount of data that we want to display to
- # the user. We load as much as we can for encoding detection
- # (Linguist) and LFS pointer parsing. All other cases where we need full
- # blob data should use load_all_data!.
+ # the user. We load as much as we can for encoding detection and LFS
+ # pointer parsing. All other cases where we need full blob data should
+ # use load_all_data!.
MAX_DATA_DISPLAY_SIZE = 10.megabytes
# These limits are used as a heuristic to ignore files which can't be LFS
diff --git a/lib/gitlab/git/blob_snippet.rb b/lib/gitlab/git/blob_snippet.rb
deleted file mode 100644
index 68116e775c6..00000000000
--- a/lib/gitlab/git/blob_snippet.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# Gitaly note: JV: no RPC's here.
-
-module Gitlab
- module Git
- class BlobSnippet
- include Linguist::BlobHelper
-
- attr_accessor :ref
- attr_accessor :lines
- attr_accessor :filename
- attr_accessor :startline
-
- def initialize(ref, lines, startline, filename)
- @ref, @lines, @startline, @filename = ref, lines, startline, filename
- end
-
- def data
- lines&.join("\n")
- end
-
- def name
- filename
- end
-
- def size
- data.length
- end
-
- def mode
- nil
- end
- end
- end
-end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 7732049b69b..9df04372cc2 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -96,10 +96,6 @@ module Gitlab
raise Gitlab::Git::CommandError.new(e.message)
end
- def circuit_breaker
- @circuit_breaker ||= Gitlab::Git::Storage::CircuitBreaker.for_storage(storage)
- end
-
def exists?
gitaly_repository_client.exists?
end
@@ -386,9 +382,9 @@ module Gitlab
end
# Returns the SHA of the most recent common ancestor of +from+ and +to+
- def merge_base(from, to)
+ def merge_base(*commits)
wrapped_gitaly_errors do
- gitaly_repository_client.find_merge_base(from, to)
+ gitaly_repository_client.find_merge_base(*commits)
end
end
diff --git a/lib/gitlab/git/storage.rb b/lib/gitlab/git/storage.rb
deleted file mode 100644
index 5933312b0b5..00000000000
--- a/lib/gitlab/git/storage.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Gitlab
- module Git
- module Storage
- class Inaccessible < StandardError
- attr_reader :retry_after
-
- def initialize(message = nil, retry_after = nil)
- super(message)
- @retry_after = retry_after
- end
- end
-
- CircuitOpen = Class.new(Inaccessible)
- Misconfiguration = Class.new(Inaccessible)
- Failing = Class.new(Inaccessible)
-
- REDIS_KEY_PREFIX = 'storage_accessible:'.freeze
- REDIS_KNOWN_KEYS = "#{REDIS_KEY_PREFIX}known_keys_set".freeze
-
- def self.redis
- Gitlab::Redis::SharedState
- end
- end
- end
-end
diff --git a/lib/gitlab/git/storage/checker.rb b/lib/gitlab/git/storage/checker.rb
deleted file mode 100644
index 391f0d70583..00000000000
--- a/lib/gitlab/git/storage/checker.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-module Gitlab
- module Git
- module Storage
- class Checker
- include CircuitBreakerSettings
-
- attr_reader :storage_path, :storage, :hostname, :logger
- METRICS_MUTEX = Mutex.new
- STORAGE_TIMING_BUCKETS = [0.1, 0.15, 0.25, 0.33, 0.5, 1, 1.5, 2.5, 5, 10, 15].freeze
-
- def self.check_all(logger = Rails.logger)
- threads = Gitlab.config.repositories.storages.keys.map do |storage_name|
- Thread.new do
- Thread.current[:result] = new(storage_name, logger).check_with_lease
- end
- end
-
- threads.map do |thread|
- thread.join
- thread[:result]
- end
- end
-
- def self.check_histogram
- @check_histogram ||=
- METRICS_MUTEX.synchronize do
- @check_histogram || Gitlab::Metrics.histogram(:circuitbreaker_storage_check_duration_seconds,
- 'Storage check time in seconds',
- {},
- STORAGE_TIMING_BUCKETS
- )
- end
- end
-
- def initialize(storage, logger = Rails.logger)
- @storage = storage
- config = Gitlab.config.repositories.storages[@storage]
- @storage_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { config.legacy_disk_path }
- @logger = logger
-
- @hostname = Gitlab::Environment.hostname
- end
-
- def check_with_lease
- lease_key = "storage_check:#{cache_key}"
- lease = Gitlab::ExclusiveLease.new(lease_key, timeout: storage_timeout)
- result = { storage: storage, success: nil }
-
- if uuid = lease.try_obtain
- result[:success] = check
-
- Gitlab::ExclusiveLease.cancel(lease_key, uuid)
- else
- logger.warn("#{hostname}: #{storage}: Skipping check, previous check still running")
- end
-
- result
- end
-
- def check
- if perform_access_check
- track_storage_accessible
- true
- else
- track_storage_inaccessible
- logger.error("#{hostname}: #{storage}: Not accessible.")
- false
- end
- end
-
- private
-
- def perform_access_check
- start_time = Gitlab::Metrics::System.monotonic_time
-
- Gitlab::Git::Storage::ForkedStorageCheck.storage_available?(storage_path, storage_timeout, access_retries)
- ensure
- execution_time = Gitlab::Metrics::System.monotonic_time - start_time
- self.class.check_histogram.observe({ storage: storage }, execution_time)
- end
-
- def track_storage_inaccessible
- first_failure = current_failure_info.first_failure || Time.now
- last_failure = Time.now
-
- Gitlab::Git::Storage.redis.with do |redis|
- redis.pipelined do
- redis.hset(cache_key, :first_failure, first_failure.to_i)
- redis.hset(cache_key, :last_failure, last_failure.to_i)
- redis.hincrby(cache_key, :failure_count, 1)
- redis.expire(cache_key, failure_reset_time)
- maintain_known_keys(redis)
- end
- end
- end
-
- def track_storage_accessible
- Gitlab::Git::Storage.redis.with do |redis|
- redis.pipelined do
- redis.hset(cache_key, :first_failure, nil)
- redis.hset(cache_key, :last_failure, nil)
- redis.hset(cache_key, :failure_count, 0)
- maintain_known_keys(redis)
- end
- end
- end
-
- def maintain_known_keys(redis)
- expire_time = Time.now.to_i + failure_reset_time
- redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, expire_time, cache_key)
- redis.zremrangebyscore(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, '-inf', Time.now.to_i)
- end
-
- def current_failure_info
- FailureInfo.load(cache_key)
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/git/storage/circuit_breaker.rb b/lib/gitlab/git/storage/circuit_breaker.rb
deleted file mode 100644
index fcee9ae566c..00000000000
--- a/lib/gitlab/git/storage/circuit_breaker.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-module Gitlab
- module Git
- module Storage
- class CircuitBreaker
- include CircuitBreakerSettings
-
- attr_reader :storage,
- :hostname
-
- delegate :last_failure, :failure_count, :no_failures?,
- to: :failure_info
-
- def self.for_storage(storage)
- cached_circuitbreakers = Gitlab::SafeRequestStore.fetch(:circuitbreaker_cache) do
- Hash.new do |hash, storage_name|
- hash[storage_name] = build(storage_name)
- end
- end
-
- cached_circuitbreakers[storage]
- end
-
- def self.build(storage, hostname = Gitlab::Environment.hostname)
- config = Gitlab.config.repositories.storages[storage]
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- if !config.present?
- NullCircuitBreaker.new(storage, hostname, error: Misconfiguration.new("Storage '#{storage}' is not configured"))
- elsif !config.legacy_disk_path.present?
- NullCircuitBreaker.new(storage, hostname, error: Misconfiguration.new("Path for storage '#{storage}' is not configured"))
- else
- new(storage, hostname)
- end
- end
- end
-
- def initialize(storage, hostname)
- @storage = storage
- @hostname = hostname
- end
-
- def perform
- return yield unless enabled?
-
- check_storage_accessible!
-
- yield
- end
-
- def circuit_broken?
- return false if no_failures?
-
- failure_count > failure_count_threshold
- end
-
- private
-
- # The circuitbreaker can be enabled for the entire fleet using a Feature
- # flag.
- #
- # Enabling it for a single host can be done setting the
- # `GIT_STORAGE_CIRCUIT_BREAKER` environment variable.
- def enabled?
- ENV['GIT_STORAGE_CIRCUIT_BREAKER'].present? || Feature.enabled?('git_storage_circuit_breaker')
- end
-
- def failure_info
- @failure_info ||= FailureInfo.load(cache_key)
- end
-
- def check_storage_accessible!
- if circuit_broken?
- raise Gitlab::Git::Storage::CircuitOpen.new("Circuit for #{storage} is broken", failure_reset_time)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/git/storage/circuit_breaker_settings.rb b/lib/gitlab/git/storage/circuit_breaker_settings.rb
deleted file mode 100644
index c9e225f187d..00000000000
--- a/lib/gitlab/git/storage/circuit_breaker_settings.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-module Gitlab
- module Git
- module Storage
- module CircuitBreakerSettings
- def failure_count_threshold
- application_settings.circuitbreaker_failure_count_threshold
- end
-
- def failure_reset_time
- application_settings.circuitbreaker_failure_reset_time
- end
-
- def storage_timeout
- application_settings.circuitbreaker_storage_timeout
- end
-
- def access_retries
- application_settings.circuitbreaker_access_retries
- end
-
- def check_interval
- application_settings.circuitbreaker_check_interval
- end
-
- def cache_key
- @cache_key ||= "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage}:#{hostname}"
- end
-
- private
-
- def application_settings
- Gitlab::CurrentSettings.current_application_settings
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/git/storage/failure_info.rb b/lib/gitlab/git/storage/failure_info.rb
deleted file mode 100644
index 1d28a850049..00000000000
--- a/lib/gitlab/git/storage/failure_info.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module Gitlab
- module Git
- module Storage
- class FailureInfo
- attr_accessor :first_failure, :last_failure, :failure_count
-
- def self.reset_all!
- Gitlab::Git::Storage.redis.with do |redis|
- all_storage_keys = redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
- redis.del(*all_storage_keys) unless all_storage_keys.empty?
- end
-
- Gitlab::SafeRequestStore.delete(:circuitbreaker_cache)
- end
-
- def self.load(cache_key)
- first_failure, last_failure, failure_count = Gitlab::Git::Storage.redis.with do |redis|
- redis.hmget(cache_key, :first_failure, :last_failure, :failure_count)
- end
-
- last_failure = Time.at(last_failure.to_i) if last_failure.present?
- first_failure = Time.at(first_failure.to_i) if first_failure.present?
-
- new(first_failure, last_failure, failure_count.to_i)
- end
-
- def initialize(first_failure, last_failure, failure_count)
- @first_failure = first_failure
- @last_failure = last_failure
- @failure_count = failure_count
- end
-
- def no_failures?
- first_failure.blank? && last_failure.blank? && failure_count == 0
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/git/storage/forked_storage_check.rb b/lib/gitlab/git/storage/forked_storage_check.rb
deleted file mode 100644
index 0a4e557b59b..00000000000
--- a/lib/gitlab/git/storage/forked_storage_check.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-module Gitlab
- module Git
- module Storage
- module ForkedStorageCheck
- extend self
-
- def storage_available?(path, timeout_seconds = 5, retries = 1)
- partial_timeout = timeout_seconds / retries
- status = timeout_check(path, partial_timeout)
-
- # If the status check did not succeed the first time, we retry a few
- # more times to avoid one-off failures
- current_attempts = 1
- while current_attempts < retries && !status.success?
- status = timeout_check(path, partial_timeout)
- current_attempts += 1
- end
-
- status.success?
- end
-
- def timeout_check(path, timeout_seconds)
- filesystem_check_pid = check_filesystem_in_process(path)
-
- deadline = timeout_seconds.seconds.from_now.utc
- wait_time = 0.01
- status = nil
-
- while status.nil?
-
- if deadline > Time.now.utc
- sleep(wait_time)
- _pid, status = Process.wait2(filesystem_check_pid, Process::WNOHANG)
- else
- Process.kill('KILL', filesystem_check_pid)
- # Blocking wait, so we are sure the process is gone before continuing
- _pid, status = Process.wait2(filesystem_check_pid)
- end
- end
-
- status
- end
-
- # This will spawn a new 2 processes to do the check:
- # The outer child (waiter) will spawn another child process (stater).
- #
- # The stater is the process is performing the actual filesystem check
- # the check might hang if the filesystem is acting up.
- # In this case we will send a `KILL` to the waiter, which will still
- # be responsive while the stater is hanging.
- def check_filesystem_in_process(path)
- spawn('ruby', '-e', ruby_check, path, [:out, :err] => '/dev/null')
- end
-
- def ruby_check
- <<~RUBY_FILESYSTEM_CHECK
- inner_pid = fork { File.stat(ARGV.first) }
- Process.waitpid(inner_pid)
- exit $?.exitstatus
- RUBY_FILESYSTEM_CHECK
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/git/storage/health.rb b/lib/gitlab/git/storage/health.rb
deleted file mode 100644
index 8e14acb4ccb..00000000000
--- a/lib/gitlab/git/storage/health.rb
+++ /dev/null
@@ -1,92 +0,0 @@
-module Gitlab
- module Git
- module Storage
- class Health
- attr_reader :storage_name, :info
-
- def self.prefix_for_storage(storage_name)
- "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}#{storage_name}:"
- end
-
- def self.for_all_storages
- storage_names = Gitlab.config.repositories.storages.keys
- results_per_storage = nil
-
- Gitlab::Git::Storage.redis.with do |redis|
- keys_per_storage = all_keys_for_storages(storage_names, redis)
- results_per_storage = load_for_keys(keys_per_storage, redis)
- end
-
- results_per_storage.map do |name, info|
- info.each { |i| i[:failure_count] = i[:failure_count].value.to_i }
- new(name, info)
- end
- end
-
- private_class_method def self.all_keys_for_storages(storage_names, redis)
- keys_per_storage = {}
- all_keys = redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
-
- storage_names.each do |storage_name|
- prefix = prefix_for_storage(storage_name)
-
- keys_per_storage[storage_name] = all_keys.select { |key| key.starts_with?(prefix) }
- end
-
- keys_per_storage
- end
-
- private_class_method def self.load_for_keys(keys_per_storage, redis)
- info_for_keys = {}
-
- redis.pipelined do
- keys_per_storage.each do |storage_name, keys_future|
- info_for_storage = keys_future.map do |key|
- { name: key, failure_count: redis.hget(key, :failure_count) }
- end
-
- info_for_keys[storage_name] = info_for_storage
- end
- end
-
- info_for_keys
- end
-
- def self.for_failing_storages
- for_all_storages.select(&:failing?)
- end
-
- def initialize(storage_name, info)
- @storage_name = storage_name
- @info = info
- end
-
- def failing_info
- @failing_info ||= info.select { |info_for_host| info_for_host[:failure_count] > 0 }
- end
-
- def failing?
- failing_info.any?
- end
-
- def failing_on_hosts
- @failing_on_hosts ||= failing_info.map do |info_for_host|
- info_for_host[:name].split(':').last
- end
- end
-
- def failing_circuit_breakers
- @failing_circuit_breakers ||= failing_on_hosts.map do |hostname|
- CircuitBreaker.build(storage_name, hostname)
- end
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def total_failures
- @total_failures ||= failing_info.sum { |info_for_host| info_for_host[:failure_count] }
- end
- # rubocop: enable CodeReuse/ActiveRecord
- end
- end
- end
-end
diff --git a/lib/gitlab/git/storage/null_circuit_breaker.rb b/lib/gitlab/git/storage/null_circuit_breaker.rb
deleted file mode 100644
index 261c936c689..00000000000
--- a/lib/gitlab/git/storage/null_circuit_breaker.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module Gitlab
- module Git
- module Storage
- class NullCircuitBreaker
- include CircuitBreakerSettings
-
- # These will have actual values
- attr_reader :storage,
- :hostname
-
- # These will always have nil values
- attr_reader :storage_path
-
- delegate :last_failure, :failure_count, :no_failures?,
- to: :failure_info
-
- def initialize(storage, hostname, error: nil)
- @storage = storage
- @hostname = hostname
- @error = error
- end
-
- def perform
- @error ? raise(@error) : yield
- end
-
- def circuit_broken?
- !!@error
- end
-
- def backing_off?
- false
- end
-
- def failure_info
- @failure_info ||=
- if circuit_broken?
- Gitlab::Git::Storage::FailureInfo.new(Time.now,
- Time.now,
- failure_count_threshold)
- else
- Gitlab::Git::Storage::FailureInfo.new(nil,
- nil,
- 0)
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index deaa14c8434..c1726659a90 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -30,5 +30,20 @@ module Gitlab
gon.current_user_avatar_url = current_user.avatar_url
end
end
+
+ # Exposes the state of a feature flag to the frontend code.
+ #
+ # name - The name of the feature flag, e.g. `my_feature`.
+ # args - Any additional arguments to pass to `Feature.enabled?`. This allows
+ # you to check if a flag is enabled for a particular user.
+ def push_frontend_feature_flag(name, *args)
+ var_name = name.to_s.camelize(:lower)
+ enabled = Feature.enabled?(name, *args)
+
+ # Here the `true` argument signals gon that the value should be merged
+ # into any existing ones, instead of overwriting them. This allows you to
+ # use this method to push multiple feature flags.
+ gon.push({ features: { var_name => enabled } }, true)
+ end
end
end
diff --git a/lib/gitlab/hook_data/base_builder.rb b/lib/gitlab/hook_data/base_builder.rb
index 4ffca356b29..1a91301e8be 100644
--- a/lib/gitlab/hook_data/base_builder.rb
+++ b/lib/gitlab/hook_data/base_builder.rb
@@ -25,6 +25,7 @@ module Gitlab
markdown_text.gsub(MARKDOWN_SIMPLE_IMAGE) do
if $~[:image]
url = $~[:url]
+ url = "#{uploads_prefix}#{url}" if url.start_with?('/uploads')
url = "/#{url}" unless url.start_with?('/')
"![#{$~[:title]}](#{Gitlab.config.gitlab.url}#{url})"
@@ -33,6 +34,16 @@ module Gitlab
end
end
end
+
+ def uploads_prefix
+ project&.full_path || ''
+ end
+
+ def project
+ return unless object.respond_to?(:project)
+
+ object.project
+ end
end
end
end
diff --git a/lib/gitlab/language_data.rb b/lib/gitlab/language_data.rb
new file mode 100644
index 00000000000..bfdd7175198
--- /dev/null
+++ b/lib/gitlab/language_data.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module LanguageData
+ EXTENSION_MUTEX = Mutex.new
+
+ class << self
+ include Gitlab::Utils::StrongMemoize
+
+ def extensions
+ EXTENSION_MUTEX.synchronize do
+ strong_memoize(:extensions) do
+ Set.new.tap do |set|
+ YAML.load_file(Rails.root.join('vendor', 'languages.yml')).each do |_name, details|
+ details['extensions']&.each do |ext|
+ next unless ext.start_with?('.')
+
+ set << ext.downcase
+ end
+ end
+ end
+ end
+ end
+ end
+
+ def clear_extensions!
+ EXTENSION_MUTEX.synchronize do
+ clear_memoization(:extensions)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index 359dd2bcbc7..a78314afdb2 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -39,7 +39,6 @@ module Gitlab
import
invites
jwt
- koding
login
notification_settings
oauth
diff --git a/lib/gitlab/storage_check.rb b/lib/gitlab/storage_check.rb
deleted file mode 100644
index fe81513c9ec..00000000000
--- a/lib/gitlab/storage_check.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require_relative 'storage_check/cli'
-require_relative 'storage_check/gitlab_caller'
-require_relative 'storage_check/option_parser'
-require_relative 'storage_check/response'
-
-module Gitlab
- module StorageCheck
- ENDPOINT = '/-/storage_check'.freeze
- Options = Struct.new(:target, :token, :interval, :dryrun)
- end
-end
diff --git a/lib/gitlab/storage_check/cli.rb b/lib/gitlab/storage_check/cli.rb
deleted file mode 100644
index 9b64c8e033a..00000000000
--- a/lib/gitlab/storage_check/cli.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-module Gitlab
- module StorageCheck
- class CLI
- def self.start!(args)
- runner = new(Gitlab::StorageCheck::OptionParser.parse!(args))
- runner.start_loop
- end
-
- attr_reader :logger, :options
-
- def initialize(options)
- @options = options
- @logger = Logger.new(STDOUT)
- end
-
- def start_loop
- logger.info "Checking #{options.target} every #{options.interval} seconds"
-
- if options.dryrun
- logger.info "Dryrun, exiting..."
- return
- end
-
- begin
- loop do
- response = GitlabCaller.new(options).call!
- log_response(response)
- update_settings(response)
-
- sleep options.interval
- end
- rescue Interrupt
- logger.info "Ending storage-check"
- end
- end
-
- def update_settings(response)
- previous_interval = options.interval
-
- if response.valid?
- options.interval = response.check_interval || previous_interval
- end
-
- if previous_interval != options.interval
- logger.info "Interval changed: #{options.interval} seconds"
- end
- end
-
- def log_response(response)
- unless response.valid?
- return logger.error("Invalid response checking nfs storage: #{response.http_response.inspect}")
- end
-
- if response.responsive_shards.any?
- logger.debug("Responsive shards: #{response.responsive_shards.join(', ')}")
- end
-
- warnings = []
- if response.skipped_shards.any?
- warnings << "Skipped shards: #{response.skipped_shards.join(', ')}"
- end
-
- if response.failing_shards.any?
- warnings << "Failing shards: #{response.failing_shards.join(', ')}"
- end
-
- logger.warn(warnings.join(' - ')) if warnings.any?
- end
- end
- end
-end
diff --git a/lib/gitlab/storage_check/gitlab_caller.rb b/lib/gitlab/storage_check/gitlab_caller.rb
deleted file mode 100644
index 44952b68844..00000000000
--- a/lib/gitlab/storage_check/gitlab_caller.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'excon'
-
-module Gitlab
- module StorageCheck
- class GitlabCaller
- def initialize(options)
- @options = options
- end
-
- def call!
- Gitlab::StorageCheck::Response.new(get_response)
- rescue Errno::ECONNREFUSED, Excon::Error
- # Server not ready, treated as invalid response.
- Gitlab::StorageCheck::Response.new(nil)
- end
-
- def get_response
- scheme, *other_parts = URI.split(@options.target)
- socket_path = if scheme == 'unix'
- other_parts.compact.join
- end
-
- connection = Excon.new(@options.target, socket: socket_path)
- connection.post(path: Gitlab::StorageCheck::ENDPOINT,
- headers: headers)
- end
-
- def headers
- @headers ||= begin
- headers = {}
- headers['Content-Type'] = headers['Accept'] = 'application/json'
- headers['TOKEN'] = @options.token if @options.token
-
- headers
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/storage_check/option_parser.rb b/lib/gitlab/storage_check/option_parser.rb
deleted file mode 100644
index 66ed7906f97..00000000000
--- a/lib/gitlab/storage_check/option_parser.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module Gitlab
- module StorageCheck
- class OptionParser
- def self.parse!(args)
- # Start out with some defaults
- options = Gitlab::StorageCheck::Options.new(nil, nil, 1, false)
-
- parser = ::OptionParser.new do |opts|
- opts.banner = "Usage: bin/storage_check [options]"
-
- opts.on('-t=string', '--target string', 'URL or socket to trigger storage check') do |value|
- options.target = value
- end
-
- opts.on('-T=string', '--token string', 'Health token to use') { |value| options.token = value }
-
- opts.on('-i=n', '--interval n', ::OptionParser::DecimalInteger, 'Seconds between checks') do |value|
- options.interval = value
- end
-
- opts.on('-d', '--dryrun', "Output what will be performed, but don't start the process") do |value|
- options.dryrun = value
- end
- end
- parser.parse!(args)
-
- unless options.target
- raise ::OptionParser::InvalidArgument.new('Provide a URI to provide checks')
- end
-
- if URI.parse(options.target).scheme.nil?
- raise ::OptionParser::InvalidArgument.new('Add the scheme to the target, `unix://`, `https://` or `http://` are supported')
- end
-
- options
- end
- end
- end
-end
diff --git a/lib/gitlab/storage_check/response.rb b/lib/gitlab/storage_check/response.rb
deleted file mode 100644
index 326ab236e3e..00000000000
--- a/lib/gitlab/storage_check/response.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'json'
-
-module Gitlab
- module StorageCheck
- class Response
- attr_reader :http_response
-
- def initialize(http_response)
- @http_response = http_response
- end
-
- def valid?
- @http_response && (200...299).cover?(@http_response.status) &&
- @http_response.headers['Content-Type'].include?('application/json') &&
- parsed_response
- end
-
- def check_interval
- return nil unless parsed_response
-
- parsed_response['check_interval']
- end
-
- def responsive_shards
- divided_results[:responsive_shards]
- end
-
- def skipped_shards
- divided_results[:skipped_shards]
- end
-
- def failing_shards
- divided_results[:failing_shards]
- end
-
- private
-
- def results
- return [] unless parsed_response
-
- parsed_response['results']
- end
-
- def divided_results
- return @divided_results if @divided_results
-
- @divided_results = {}
- @divided_results[:responsive_shards] = []
- @divided_results[:skipped_shards] = []
- @divided_results[:failing_shards] = []
-
- results.each do |info|
- name = info['storage']
-
- case info['success']
- when true
- @divided_results[:responsive_shards] << name
- when false
- @divided_results[:failing_shards] << name
- else
- @divided_results[:skipped_shards] << name
- end
- end
-
- @divided_results
- end
-
- def parsed_response
- return @parsed_response if defined?(@parsed_response)
-
- @parsed_response = JSON.parse(@http_response.body)
- rescue JSON::JSONError
- @parsed_response = nil
- end
- end
- end
-end
diff --git a/lib/google_api/auth.rb b/lib/google_api/auth.rb
index 1aeaa387a49..e724e58e9ca 100644
--- a/lib/google_api/auth.rb
+++ b/lib/google_api/auth.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GoogleApi
class Auth
attr_reader :access_token, :redirect_uri, :state
diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb
index 77b6610286f..e74ff6a9129 100644
--- a/lib/google_api/cloud_platform/client.rb
+++ b/lib/google_api/cloud_platform/client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'google/apis/compute_v1'
require 'google/apis/container_v1'
require 'google/apis/cloudbilling_v1'
diff --git a/lib/haml_lint/inline_javascript.rb b/lib/haml_lint/inline_javascript.rb
index adbed20f152..5ecd6169ecf 100644
--- a/lib/haml_lint/inline_javascript.rb
+++ b/lib/haml_lint/inline_javascript.rb
@@ -1,4 +1,7 @@
-unless Rails.env.production? # rubocop:disable Naming/FileName
+# rubocop:disable Naming/FileName
+# frozen_string_literal: true
+
+unless Rails.env.production?
require 'haml_lint/haml_visitor'
require 'haml_lint/linter'
require 'haml_lint/linter_registry'
diff --git a/lib/json_web_token/rsa_token.rb b/lib/json_web_token/rsa_token.rb
index d6d6af7089c..160e1e506f1 100644
--- a/lib/json_web_token/rsa_token.rb
+++ b/lib/json_web_token/rsa_token.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module JSONWebToken
class RSAToken < Token
attr_reader :key_file
diff --git a/lib/json_web_token/token.rb b/lib/json_web_token/token.rb
index 5b67715b0b2..ce5d6f248d0 100644
--- a/lib/json_web_token/token.rb
+++ b/lib/json_web_token/token.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module JSONWebToken
class Token
attr_accessor :issuer, :subject, :audience, :id
diff --git a/lib/mattermost/client.rb b/lib/mattermost/client.rb
index d80cd7d2a4e..293d0c563c5 100644
--- a/lib/mattermost/client.rb
+++ b/lib/mattermost/client.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mattermost
ClientError = Class.new(Mattermost::Error)
diff --git a/lib/mattermost/command.rb b/lib/mattermost/command.rb
index 704813dfdf0..a02745486d6 100644
--- a/lib/mattermost/command.rb
+++ b/lib/mattermost/command.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mattermost
class Command < Client
def create(params)
diff --git a/lib/mattermost/error.rb b/lib/mattermost/error.rb
index dee6deb7974..054bd5457bd 100644
--- a/lib/mattermost/error.rb
+++ b/lib/mattermost/error.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mattermost
Error = Class.new(StandardError)
end
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
index 2aa7a2f64d8..e2083848a8d 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mattermost
class NoSessionError < Mattermost::Error
def message
diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb
index 95c2f6f9d6b..58120178f50 100644
--- a/lib/mattermost/team.rb
+++ b/lib/mattermost/team.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Mattermost
class Team < Client
# Returns all teams that the current user is a member of
diff --git a/lib/microsoft_teams/activity.rb b/lib/microsoft_teams/activity.rb
index d2c420efdaf..207e90d2638 100644
--- a/lib/microsoft_teams/activity.rb
+++ b/lib/microsoft_teams/activity.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MicrosoftTeams
class Activity
def initialize(title:, subtitle:, text:, image:)
diff --git a/lib/microsoft_teams/notifier.rb b/lib/microsoft_teams/notifier.rb
index 226ee1373db..c7dec09ba6b 100644
--- a/lib/microsoft_teams/notifier.rb
+++ b/lib/microsoft_teams/notifier.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MicrosoftTeams
class Notifier
def initialize(webhook)
diff --git a/lib/object_storage/direct_upload.rb b/lib/object_storage/direct_upload.rb
index 97f56e10ccf..fd26663fef0 100644
--- a/lib/object_storage/direct_upload.rb
+++ b/lib/object_storage/direct_upload.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ObjectStorage
#
# The DirectUpload c;ass generates a set of presigned URLs
diff --git a/lib/omni_auth/strategies/bitbucket.rb b/lib/omni_auth/strategies/bitbucket.rb
index ce1bdfe6ee4..6c914b4222a 100644
--- a/lib/omni_auth/strategies/bitbucket.rb
+++ b/lib/omni_auth/strategies/bitbucket.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'omniauth-oauth2'
module OmniAuth
diff --git a/lib/omni_auth/strategies/jwt.rb b/lib/omni_auth/strategies/jwt.rb
index ebdb5c7faf0..a792903fde7 100644
--- a/lib/omni_auth/strategies/jwt.rb
+++ b/lib/omni_auth/strategies/jwt.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'omniauth'
require 'jwt'
diff --git a/lib/peek/rblineprof/custom_controller_helpers.rb b/lib/peek/rblineprof/custom_controller_helpers.rb
index 9beb442bfa3..581cc6a37b4 100644
--- a/lib/peek/rblineprof/custom_controller_helpers.rb
+++ b/lib/peek/rblineprof/custom_controller_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Peek
module Rblineprof
module CustomControllerHelpers
@@ -41,7 +43,7 @@ module Peek
]
end.sort_by{ |a,b,c,d,e,f| -f }
- output = "<div class='modal-dialog modal-xl'><div class='modal-content'>"
+ output = ["<div class='modal-dialog modal-xl'><div class='modal-content'>"]
output << "<div class='modal-header'>"
output << "<h4>Line profiling: #{human_description(params[:lineprofiler])}</h4>"
output << "<button class='close' type='button' data-dismiss='modal' aria-label='close'><span aria-hidden='true'>&times;</span></button>"
@@ -93,7 +95,7 @@ module Peek
output << "</div></div></div>"
- response.body += "<div class='modal' id='modal-peek-line-profile' tabindex=-1>#{output}</div>".html_safe
+ response.body += "<div class='modal' id='modal-peek-line-profile' tabindex=-1>#{output.join}</div>".html_safe
end
ret
diff --git a/lib/peek/views/gitaly.rb b/lib/peek/views/gitaly.rb
index ab35f7a2258..860963ef94f 100644
--- a/lib/peek/views/gitaly.rb
+++ b/lib/peek/views/gitaly.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Peek
module Views
class Gitaly < View
diff --git a/lib/peek/views/host.rb b/lib/peek/views/host.rb
index 43c8a35c7ea..b77355ea11b 100644
--- a/lib/peek/views/host.rb
+++ b/lib/peek/views/host.rb
@@ -1,8 +1,13 @@
+# frozen_string_literal: true
+
module Peek
module Views
class Host < View
def results
- { hostname: Gitlab::Environment.hostname }
+ {
+ hostname: Gitlab::Environment.hostname,
+ canary: Gitlab::Utils.to_boolean(ENV['CANARY'])
+ }
end
end
end
diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb
index e877ab10248..e2a7d3ef5ba 100644
--- a/lib/rouge/formatters/html_gitlab.rb
+++ b/lib/rouge/formatters/html_gitlab.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Rouge
module Formatters
class HTMLGitlab < Rouge::Formatters::HTML
diff --git a/lib/rouge/plugins/common_mark.rb b/lib/rouge/plugins/common_mark.rb
index 8f9de061124..d240df5a0e0 100644
--- a/lib/rouge/plugins/common_mark.rb
+++ b/lib/rouge/plugins/common_mark.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# A rouge plugin for CommonMark markdown engine.
# Used to highlight code generated by CommonMark.
diff --git a/lib/rspec_flaky/config.rb b/lib/rspec_flaky/config.rb
index 06e96f969f1..55c1d4747b4 100644
--- a/lib/rspec_flaky/config.rb
+++ b/lib/rspec_flaky/config.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RspecFlaky
class Config
def self.generate_report?
diff --git a/lib/rspec_flaky/example.rb b/lib/rspec_flaky/example.rb
index b6e790cbbab..3c1b05257a0 100644
--- a/lib/rspec_flaky/example.rb
+++ b/lib/rspec_flaky/example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RspecFlaky
# This is a wrapper class for RSpec::Core::Example
class Example
diff --git a/lib/rspec_flaky/flaky_example.rb b/lib/rspec_flaky/flaky_example.rb
index 6be24014d89..da5dbf06bc9 100644
--- a/lib/rspec_flaky/flaky_example.rb
+++ b/lib/rspec_flaky/flaky_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RspecFlaky
# This represents a flaky RSpec example and is mainly meant to be saved in a JSON file
class FlakyExample < OpenStruct
diff --git a/lib/rspec_flaky/flaky_examples_collection.rb b/lib/rspec_flaky/flaky_examples_collection.rb
index dea23c325be..290a51766e9 100644
--- a/lib/rspec_flaky/flaky_examples_collection.rb
+++ b/lib/rspec_flaky/flaky_examples_collection.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'active_support/hash_with_indifferent_access'
require_relative 'flaky_example'
diff --git a/lib/rspec_flaky/listener.rb b/lib/rspec_flaky/listener.rb
index 9cd0c38cb55..19cc0baa2d3 100644
--- a/lib/rspec_flaky/listener.rb
+++ b/lib/rspec_flaky/listener.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'json'
require_dependency 'rspec_flaky/config'
diff --git a/lib/rspec_flaky/report.rb b/lib/rspec_flaky/report.rb
index 1c362fdd20d..9a0fb88c424 100644
--- a/lib/rspec_flaky/report.rb
+++ b/lib/rspec_flaky/report.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'json'
require 'time'
diff --git a/lib/system_check/app/active_users_check.rb b/lib/system_check/app/active_users_check.rb
index 1d72c8d6903..8446c2fc2c8 100644
--- a/lib/system_check/app/active_users_check.rb
+++ b/lib/system_check/app/active_users_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class ActiveUsersCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/database_config_exists_check.rb b/lib/system_check/app/database_config_exists_check.rb
index d1fae192350..1769145ed63 100644
--- a/lib/system_check/app/database_config_exists_check.rb
+++ b/lib/system_check/app/database_config_exists_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class DatabaseConfigExistsCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/git_config_check.rb b/lib/system_check/app/git_config_check.rb
index d08a81639e3..4e8d607096c 100644
--- a/lib/system_check/app/git_config_check.rb
+++ b/lib/system_check/app/git_config_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class GitConfigCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/git_user_default_ssh_config_check.rb b/lib/system_check/app/git_user_default_ssh_config_check.rb
index ad41760dff2..6cd53779bfd 100644
--- a/lib/system_check/app/git_user_default_ssh_config_check.rb
+++ b/lib/system_check/app/git_user_default_ssh_config_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class GitUserDefaultSSHConfigCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/git_version_check.rb b/lib/system_check/app/git_version_check.rb
index 44ec888c197..994af3ab53e 100644
--- a/lib/system_check/app/git_version_check.rb
+++ b/lib/system_check/app/git_version_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class GitVersionCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/gitlab_config_exists_check.rb b/lib/system_check/app/gitlab_config_exists_check.rb
index 247aa0994e4..1cc5ead0d89 100644
--- a/lib/system_check/app/gitlab_config_exists_check.rb
+++ b/lib/system_check/app/gitlab_config_exists_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class GitlabConfigExistsCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/gitlab_config_up_to_date_check.rb b/lib/system_check/app/gitlab_config_up_to_date_check.rb
index c609e48e133..58c7e3039c8 100644
--- a/lib/system_check/app/gitlab_config_up_to_date_check.rb
+++ b/lib/system_check/app/gitlab_config_up_to_date_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class GitlabConfigUpToDateCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/init_script_exists_check.rb b/lib/system_check/app/init_script_exists_check.rb
index d246e058e86..d36dbe7d67d 100644
--- a/lib/system_check/app/init_script_exists_check.rb
+++ b/lib/system_check/app/init_script_exists_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class InitScriptExistsCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/init_script_up_to_date_check.rb b/lib/system_check/app/init_script_up_to_date_check.rb
index 53a47eb0f42..569c41df6e4 100644
--- a/lib/system_check/app/init_script_up_to_date_check.rb
+++ b/lib/system_check/app/init_script_up_to_date_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class InitScriptUpToDateCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/log_writable_check.rb b/lib/system_check/app/log_writable_check.rb
index 3e0c436d6ee..e26ad143eb8 100644
--- a/lib/system_check/app/log_writable_check.rb
+++ b/lib/system_check/app/log_writable_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class LogWritableCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/migrations_are_up_check.rb b/lib/system_check/app/migrations_are_up_check.rb
index 5eedbacce77..b12e9ac6bba 100644
--- a/lib/system_check/app/migrations_are_up_check.rb
+++ b/lib/system_check/app/migrations_are_up_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class MigrationsAreUpCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/orphaned_group_members_check.rb b/lib/system_check/app/orphaned_group_members_check.rb
index 2b46d36fe51..3e6ffb8190b 100644
--- a/lib/system_check/app/orphaned_group_members_check.rb
+++ b/lib/system_check/app/orphaned_group_members_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class OrphanedGroupMembersCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/projects_have_namespace_check.rb b/lib/system_check/app/projects_have_namespace_check.rb
index a6ec9f7665c..2bf2529acf1 100644
--- a/lib/system_check/app/projects_have_namespace_check.rb
+++ b/lib/system_check/app/projects_have_namespace_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class ProjectsHaveNamespaceCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/redis_version_check.rb b/lib/system_check/app/redis_version_check.rb
index a0610e73576..890f8b44d13 100644
--- a/lib/system_check/app/redis_version_check.rb
+++ b/lib/system_check/app/redis_version_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class RedisVersionCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/ruby_version_check.rb b/lib/system_check/app/ruby_version_check.rb
index 57bbabece1f..d73c39f2c3f 100644
--- a/lib/system_check/app/ruby_version_check.rb
+++ b/lib/system_check/app/ruby_version_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class RubyVersionCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/tmp_writable_check.rb b/lib/system_check/app/tmp_writable_check.rb
index 99a75e57abf..6687df091d3 100644
--- a/lib/system_check/app/tmp_writable_check.rb
+++ b/lib/system_check/app/tmp_writable_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class TmpWritableCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/uploads_directory_exists_check.rb b/lib/system_check/app/uploads_directory_exists_check.rb
index 7026d0ba075..940eff9d4cf 100644
--- a/lib/system_check/app/uploads_directory_exists_check.rb
+++ b/lib/system_check/app/uploads_directory_exists_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class UploadsDirectoryExistsCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/uploads_path_permission_check.rb b/lib/system_check/app/uploads_path_permission_check.rb
index 7df6c060254..4a49f3bc2bb 100644
--- a/lib/system_check/app/uploads_path_permission_check.rb
+++ b/lib/system_check/app/uploads_path_permission_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class UploadsPathPermissionCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/app/uploads_path_tmp_permission_check.rb b/lib/system_check/app/uploads_path_tmp_permission_check.rb
index b276a81eac1..ae374f4707c 100644
--- a/lib/system_check/app/uploads_path_tmp_permission_check.rb
+++ b/lib/system_check/app/uploads_path_tmp_permission_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module App
class UploadsPathTmpPermissionCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/base_check.rb b/lib/system_check/base_check.rb
index 0f5742dd67f..e06245294c4 100644
--- a/lib/system_check/base_check.rb
+++ b/lib/system_check/base_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
# Base class for Checks. You must inherit from here
# and implement the methods below when necessary
diff --git a/lib/system_check/helpers.rb b/lib/system_check/helpers.rb
index 6227e461d24..07d479848fe 100644
--- a/lib/system_check/helpers.rb
+++ b/lib/system_check/helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module Helpers
include ::Gitlab::TaskHelpers
diff --git a/lib/system_check/incoming_email/foreman_configured_check.rb b/lib/system_check/incoming_email/foreman_configured_check.rb
index 1db7bf2b782..944913087da 100644
--- a/lib/system_check/incoming_email/foreman_configured_check.rb
+++ b/lib/system_check/incoming_email/foreman_configured_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module IncomingEmail
class ForemanConfiguredCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/incoming_email/imap_authentication_check.rb b/lib/system_check/incoming_email/imap_authentication_check.rb
index 3550c5796b0..613c2296375 100644
--- a/lib/system_check/incoming_email/imap_authentication_check.rb
+++ b/lib/system_check/incoming_email/imap_authentication_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module IncomingEmail
class ImapAuthenticationCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/incoming_email/initd_configured_check.rb b/lib/system_check/incoming_email/initd_configured_check.rb
index ea23b8ef49c..acb4b5a9e74 100644
--- a/lib/system_check/incoming_email/initd_configured_check.rb
+++ b/lib/system_check/incoming_email/initd_configured_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module IncomingEmail
class InitdConfiguredCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/incoming_email/mail_room_running_check.rb b/lib/system_check/incoming_email/mail_room_running_check.rb
index c1807501829..b7aead4624e 100644
--- a/lib/system_check/incoming_email/mail_room_running_check.rb
+++ b/lib/system_check/incoming_email/mail_room_running_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module IncomingEmail
class MailRoomRunningCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/orphans/namespace_check.rb b/lib/system_check/orphans/namespace_check.rb
index 09b57c7b408..53b2d8fd5b3 100644
--- a/lib/system_check/orphans/namespace_check.rb
+++ b/lib/system_check/orphans/namespace_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module Orphans
class NamespaceCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/orphans/repository_check.rb b/lib/system_check/orphans/repository_check.rb
index 2695c658874..ef8fe945f61 100644
--- a/lib/system_check/orphans/repository_check.rb
+++ b/lib/system_check/orphans/repository_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
module Orphans
class RepositoryCheck < SystemCheck::BaseCheck
diff --git a/lib/system_check/simple_executor.rb b/lib/system_check/simple_executor.rb
index 99c9e984107..11818ae54f8 100644
--- a/lib/system_check/simple_executor.rb
+++ b/lib/system_check/simple_executor.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SystemCheck
# Simple Executor is current default executor for GitLab
# It is a simple port from display logic in the old check.rake
diff --git a/locale/ar_SA/gitlab.po b/locale/ar_SA/gitlab.po
index d196fac6c60..903d72d9ea8 100644
--- a/locale/ar_SA/gitlab.po
+++ b/locale/ar_SA/gitlab.po
@@ -4410,12 +4410,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5391,9 +5385,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6942,9 +6933,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po
index 0d5026c0f4a..e3a5e191023 100644
--- a/locale/bg/gitlab.po
+++ b/locale/bg/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr "ÐаÑтройка на „Koding“"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/ca_ES/gitlab.po b/locale/ca_ES/gitlab.po
index 1a052348522..91d5fb17000 100644
--- a/locale/ca_ES/gitlab.po
+++ b/locale/ca_ES/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/cs_CZ/gitlab.po b/locale/cs_CZ/gitlab.po
index 3a2267c4bf7..867b9b27ad2 100644
--- a/locale/cs_CZ/gitlab.po
+++ b/locale/cs_CZ/gitlab.po
@@ -4338,12 +4338,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5311,9 +5305,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6858,9 +6849,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/da_DK/gitlab.po b/locale/da_DK/gitlab.po
index 1a6e564ed36..f8412f28cbe 100644
--- a/locale/da_DK/gitlab.po
+++ b/locale/da_DK/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po
index c27a0dea04d..5f9fbc1cc39 100644
--- a/locale/de/gitlab.po
+++ b/locale/de/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr "Koding einrichten"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/en/gitlab.po b/locale/en/gitlab.po
index b50685514e1..5e14e94e6fc 100644
--- a/locale/en/gitlab.po
+++ b/locale/en/gitlab.po
@@ -882,9 +882,6 @@ msgstr ""
msgid "Set up CI"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up auto deploy"
msgstr ""
diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po
index d0a67a1d089..15d537ff3b4 100644
--- a/locale/eo/gitlab.po
+++ b/locale/eo/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr "Agordi „Koding“"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po
index 6ce5b6a3aff..0d8ee05b364 100644
--- a/locale/es/gitlab.po
+++ b/locale/es/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr "Junio"
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr "Configurar Koding"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/et_EE/gitlab.po b/locale/et_EE/gitlab.po
index 8e4edc84c83..1f699a8e06d 100644
--- a/locale/et_EE/gitlab.po
+++ b/locale/et_EE/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po
index 73eeb56bea2..c9b371f8df3 100644
--- a/locale/fil_PH/gitlab.po
+++ b/locale/fil_PH/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po
index 93b30d0ef31..1925235d48a 100644
--- a/locale/fr/gitlab.po
+++ b/locale/fr/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr "juin"
msgid "June"
msgstr "juin"
-msgid "Koding"
-msgstr "Koding"
-
-msgid "Koding Dashboard"
-msgstr "Tableau de bord Koding"
-
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5231,9 +5225,6 @@ msgstr "Un ou plusieurs de vos projets Bitbucket ne peuvent être importés dire
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Un ou plusieurs de vos projets Google Code ne peuvent être importés directement dans GitLab parce qu’ils utilisent Subversion ou Mercurial comme gestionnaire de versions au lieu de Git."
-msgid "Online IDE integration settings."
-msgstr "Paramètres d’intégration de l’EDI en ligne."
-
msgid "Only admins"
msgstr "Seulement les administrateurs"
@@ -6774,9 +6765,6 @@ msgstr "Définissez les exigences pour la connexion d’un utilisateur. Activez
msgid "Set up CI/CD"
msgstr "Configuration CI/CD"
-msgid "Set up Koding"
-msgstr "Configurer Koding"
-
msgid "Set up a %{type} Runner manually"
msgstr "Configurer manuellement un exécuteur %{type}"
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 8b7d4b0f17e..b8c4eddbec1 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -132,23 +132,12 @@ msgstr ""
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr ""
-msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt."
-msgstr ""
-
-msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved."
-msgstr ""
-
msgid "%{openOrClose} %{noteable}"
msgstr ""
msgid "%{percent}%% complete"
msgstr ""
-msgid "%{storage_name}: failed storage access attempt on host:"
-msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
msgstr[0] ""
@@ -314,9 +303,6 @@ msgstr ""
msgid "Access expiration date"
msgstr ""
-msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again."
-msgstr ""
-
msgid "Account"
msgstr ""
@@ -401,9 +387,6 @@ msgstr ""
msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running."
msgstr ""
-msgid "AdminHealthPageLink|health page"
-msgstr ""
-
msgid "AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered."
msgstr ""
@@ -440,7 +423,7 @@ msgstr ""
msgid "AdminUsers|To confirm, type %{username}"
msgstr ""
-msgid "Advanced"
+msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
msgstr ""
msgid "Advanced settings"
@@ -461,6 +444,9 @@ msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow projects within this group to use Git LFS"
+msgstr ""
+
msgid "Allow public access to pipelines and job details, including output logs and artifacts"
msgstr ""
@@ -470,12 +456,21 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow users to request access"
+msgstr ""
+
+msgid "Allow users to request access if visibility is public or internal."
+msgstr ""
+
msgid "Allows you to add and manage Kubernetes clusters."
msgstr ""
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
msgstr ""
+msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
+msgstr ""
+
msgid "An application called %{link_to_client} is requesting access to your GitLab account."
msgstr ""
@@ -1162,7 +1157,7 @@ msgstr ""
msgid "Chat"
msgstr ""
-msgid "Check interval"
+msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr ""
msgid "Checking %{text} availability…"
@@ -1300,9 +1295,6 @@ msgstr ""
msgid "CiVariable|Validation failed"
msgstr ""
-msgid "CircuitBreakerApiLink|circuitbreaker api"
-msgstr ""
-
msgid "Clear search"
msgstr ""
@@ -1844,7 +1836,7 @@ msgstr ""
msgid "Configure push mirrors."
msgstr ""
-msgid "Configure storage path and circuit breaker settings."
+msgid "Configure storage path settings."
msgstr ""
msgid "Configure the way a user creates a new account."
@@ -2497,9 +2489,6 @@ msgstr ""
msgid "Enable group Runners"
msgstr ""
-msgid "Enable or disable certain group features and choose access levels."
-msgstr ""
-
msgid "Enable or disable version check and usage ping."
msgstr ""
@@ -2917,9 +2906,6 @@ msgstr ""
msgid "Git revision"
msgstr ""
-msgid "Git storage health information has been reset"
-msgstr ""
-
msgid "Git strategy for pipelines"
msgstr ""
@@ -3010,6 +2996,9 @@ msgstr ""
msgid "Group avatar"
msgstr ""
+msgid "Group description (optional)"
+msgstr ""
+
msgid "Group details"
msgstr ""
@@ -3019,6 +3008,9 @@ msgstr ""
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
+msgid "Group name"
+msgstr ""
+
msgid "Group: %{group_name}"
msgstr ""
@@ -3034,9 +3026,6 @@ msgstr ""
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
-msgid "GroupSettings|Share with group lock"
-msgstr ""
-
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -3450,12 +3439,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -3522,6 +3505,9 @@ msgstr ""
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr ""
+msgid "Large File Storage"
+msgstr ""
+
msgid "Last %d day"
msgid_plural "Last %d days"
msgstr[0] ""
@@ -3736,9 +3722,6 @@ msgstr ""
msgid "Max access level"
msgstr ""
-msgid "Maximum git storage failures"
-msgstr ""
-
msgid "Maximum job timeout"
msgstr ""
@@ -3934,6 +3917,9 @@ msgstr ""
msgid "Name:"
msgstr ""
+msgid "Naming, visibility"
+msgstr ""
+
msgid "Nav|Help"
msgstr ""
@@ -4206,9 +4192,6 @@ msgstr ""
msgid "November"
msgstr ""
-msgid "Number of access attempts"
-msgstr ""
-
msgid "Oct"
msgstr ""
@@ -4229,9 +4212,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -4268,6 +4248,9 @@ msgstr ""
msgid "Operations"
msgstr ""
+msgid "Operations Dashboard"
+msgstr ""
+
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
msgstr ""
@@ -4319,6 +4302,9 @@ msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
+msgid "Path, transfer, remove"
+msgstr ""
+
msgid "Path:"
msgstr ""
@@ -4346,6 +4332,9 @@ msgstr ""
msgid "Permissions"
msgstr ""
+msgid "Permissions, LFS, 2FA"
+msgstr ""
+
msgid "Personal Access Token"
msgstr ""
@@ -5080,12 +5069,21 @@ msgstr ""
msgid "Remove avatar"
msgstr ""
+msgid "Remove group"
+msgstr ""
+
msgid "Remove priority"
msgstr ""
msgid "Remove project"
msgstr ""
+msgid "Removed group can not be restored!"
+msgstr ""
+
+msgid "Removing group will cause all child projects and resources to be removed."
+msgstr ""
+
msgid "Rename"
msgstr ""
@@ -5152,10 +5150,10 @@ msgstr ""
msgid "Requests Profiles"
msgstr ""
-msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
+msgid "Require all users in this group to setup Two-factor authentication"
msgstr ""
-msgid "Reset git storage health information"
+msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr ""
msgid "Reset health check access token"
@@ -5370,12 +5368,6 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
-msgid "Seconds before reseting failure information"
-msgstr ""
-
-msgid "Seconds to wait for a storage access attempt"
-msgstr ""
-
msgid "Secret"
msgstr ""
@@ -5466,9 +5458,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
@@ -5980,12 +5969,6 @@ msgstr ""
msgid "The maximum file size allowed is 200KB."
msgstr ""
-msgid "The number of attempts GitLab will make to access a storage."
-msgstr ""
-
-msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
-msgstr ""
-
msgid "The path to CI config file. Defaults to <code>.gitlab-ci.yml</code>"
msgstr ""
@@ -6031,15 +6014,6 @@ msgstr ""
msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running."
msgstr ""
-msgid "The time in seconds GitLab will keep failure information. When no failures occur during this time, information about the mount is reset."
-msgstr ""
-
-msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised."
-msgstr ""
-
-msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check."
-msgstr ""
-
msgid "The time taken by each data entry gathered by that stage."
msgstr ""
@@ -6079,9 +6053,6 @@ msgstr ""
msgid "There are no unstaged changes"
msgstr ""
-msgid "There are problems accessing Git storage: "
-msgstr ""
-
msgid "There was an error loading users activity calendar."
msgstr ""
@@ -6235,6 +6206,9 @@ msgstr ""
msgid "This runner will only run on pipelines triggered on protected branches"
msgstr ""
+msgid "This setting can be overridden in each project."
+msgstr ""
+
msgid "This source diff could not be displayed because it is too large."
msgstr ""
@@ -6253,6 +6227,9 @@ msgstr ""
msgid "Time before an issue starts implementation"
msgstr ""
+msgid "Time before enforced"
+msgstr ""
+
msgid "Time between merge request creation and merge/close"
msgstr ""
@@ -6536,6 +6513,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor authentication"
+msgstr ""
+
msgid "Type"
msgstr ""
@@ -6602,7 +6582,7 @@ msgstr ""
msgid "Update now"
msgstr ""
-msgid "Update your group name, description, avatar, and other general settings."
+msgid "Update your group name, description, avatar, and visibility."
msgstr ""
msgid "Updating"
@@ -6758,6 +6738,9 @@ msgstr ""
msgid "Visibility and access controls"
msgstr ""
+msgid "Visibility level"
+msgstr ""
+
msgid "Visibility level:"
msgstr ""
@@ -6800,6 +6783,9 @@ msgstr ""
msgid "When enabled, users cannot use GitLab until the terms have been accepted."
msgstr ""
+msgid "Who can see this group?"
+msgstr ""
+
msgid "Wiki"
msgstr ""
diff --git a/locale/gl_ES/gitlab.po b/locale/gl_ES/gitlab.po
index c77dc236458..44c47901b56 100644
--- a/locale/gl_ES/gitlab.po
+++ b/locale/gl_ES/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/he_IL/gitlab.po b/locale/he_IL/gitlab.po
index ab014982a72..7cff1c5d712 100644
--- a/locale/he_IL/gitlab.po
+++ b/locale/he_IL/gitlab.po
@@ -4338,12 +4338,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5311,9 +5305,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6858,9 +6849,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po
index d5c48520155..2f3c35e4253 100644
--- a/locale/id_ID/gitlab.po
+++ b/locale/id_ID/gitlab.po
@@ -4230,12 +4230,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5191,9 +5185,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6732,9 +6723,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po
index 87bcd939fb1..984a200c958 100644
--- a/locale/it/gitlab.po
+++ b/locale/it/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr "Giu"
msgid "June"
msgstr "Giugno"
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr "Configura Koding"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po
index ee5ea023fb5..ac940230ac2 100644
--- a/locale/ja/gitlab.po
+++ b/locale/ja/gitlab.po
@@ -4230,12 +4230,6 @@ msgstr "6月"
msgid "June"
msgstr "6月"
-msgid "Koding"
-msgstr "Koding"
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5191,9 +5185,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr "オンライン IDE çµ±åˆè¨­å®šã€‚"
-
msgid "Only admins"
msgstr ""
@@ -6732,9 +6723,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr "CI/CD を設定"
-msgid "Set up Koding"
-msgstr "Koding を設定"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po
index 3c3bcf9688a..a50f5bfacef 100644
--- a/locale/ko/gitlab.po
+++ b/locale/ko/gitlab.po
@@ -4230,12 +4230,6 @@ msgstr "6ì›”"
msgid "June"
msgstr "6ì›”"
-msgid "Koding"
-msgstr "Koding"
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5191,9 +5185,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr "온ë¼ì¸ IDE 통합 설정."
-
msgid "Only admins"
msgstr ""
@@ -6732,9 +6723,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr "Koding 설정"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/mn_MN/gitlab.po b/locale/mn_MN/gitlab.po
index 0ed5a747cfd..c256a2d2b1f 100644
--- a/locale/mn_MN/gitlab.po
+++ b/locale/mn_MN/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/nb_NO/gitlab.po b/locale/nb_NO/gitlab.po
index b04b3bb68ef..9be8a4e8d0b 100644
--- a/locale/nb_NO/gitlab.po
+++ b/locale/nb_NO/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po
index f354ca50f32..2ad2eaafc33 100644
--- a/locale/nl_NL/gitlab.po
+++ b/locale/nl_NL/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po
index 1d6dc4c4399..a4bd1fe747b 100644
--- a/locale/pl_PL/gitlab.po
+++ b/locale/pl_PL/gitlab.po
@@ -4338,12 +4338,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5311,9 +5305,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6858,9 +6849,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po
index c76c639e8db..79e365319c9 100644
--- a/locale/pt_BR/gitlab.po
+++ b/locale/pt_BR/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr "Jun"
msgid "June"
msgstr "Junho"
-msgid "Koding"
-msgstr "Koding"
-
-msgid "Koding Dashboard"
-msgstr "Painel de controle do Koding"
-
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5231,9 +5225,6 @@ msgstr "Um ou mais dos seus projetos do Bitbucket não podem ser importados dire
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Um ou mais dos seus projetos do Google Code não podem ser importados diretamente no GitLab porque eles usam Subversion ou Mercurial para o controle de versão, ao invés de Git."
-msgid "Online IDE integration settings."
-msgstr "Configurações de integração on-line do IDE."
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr "Definir requisitos para um usuário entrar. Ative a autenticação obrig
msgid "Set up CI/CD"
msgstr "Configurar CI/CD"
-msgid "Set up Koding"
-msgstr "Configurar Koding"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/ro_RO/gitlab.po b/locale/ro_RO/gitlab.po
index bae64f360fc..49613a3f462 100644
--- a/locale/ro_RO/gitlab.po
+++ b/locale/ro_RO/gitlab.po
@@ -4302,12 +4302,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5271,9 +5265,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6816,9 +6807,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po
index bc2c26da457..b8a4805498b 100644
--- a/locale/ru/gitlab.po
+++ b/locale/ru/gitlab.po
@@ -4338,12 +4338,6 @@ msgstr "Июн."
msgid "June"
msgstr "Июнь"
-msgid "Koding"
-msgstr "Koding"
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5311,9 +5305,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6858,9 +6849,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr "ÐаÑтройка CI/CD"
-msgid "Set up Koding"
-msgstr "ÐаÑтройка Koding"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/sq_AL/gitlab.po b/locale/sq_AL/gitlab.po
index 42eeed11534..633b2a6bda7 100644
--- a/locale/sq_AL/gitlab.po
+++ b/locale/sq_AL/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po
index d1087ffd29e..723f4876053 100644
--- a/locale/tr_TR/gitlab.po
+++ b/locale/tr_TR/gitlab.po
@@ -4266,12 +4266,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5231,9 +5225,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6774,9 +6765,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr ""
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index 33019a3e5a8..99ba26152ee 100644
--- a/locale/uk/gitlab.po
+++ b/locale/uk/gitlab.po
@@ -4338,12 +4338,6 @@ msgstr "чер."
msgid "June"
msgstr "червень"
-msgid "Koding"
-msgstr "Koding"
-
-msgid "Koding Dashboard"
-msgstr "Панель ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Koding"
-
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5311,9 +5305,6 @@ msgstr "Один або декілька ваших проектів Bitbucket Ð
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "Один або декілька ваших проектів Google Code не можна імпортувати безпоÑередньо в GitLab, оÑкільки вони викориÑтовують Subversion або Mercurial Ð´Ð»Ñ ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»ÑŽ верÑій заміÑть Git."
-msgid "Online IDE integration settings."
-msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð½Ñ‚ÐµÐ³Ñ€Ð°Ñ†Ñ–Ñ— із онлайн IDE."
-
msgid "Only admins"
msgstr "Тільки Ðдміни"
@@ -6858,9 +6849,6 @@ msgstr "Ð’Ñтановіть вимоги Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ кориÑтувач
msgid "Set up CI/CD"
msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ CI/CD"
-msgid "Set up Koding"
-msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Koding"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po
index 861e459bcac..2d3aeca7cc7 100644
--- a/locale/zh_CN/gitlab.po
+++ b/locale/zh_CN/gitlab.po
@@ -4230,12 +4230,6 @@ msgstr "å…­"
msgid "June"
msgstr "六月"
-msgid "Koding"
-msgstr "Koding"
-
-msgid "Koding Dashboard"
-msgstr "Koding仪表æ¿"
-
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5191,9 +5185,6 @@ msgstr "您的一个或多个Bitbucket项目无法直接导入GitLab,因为它
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "您的一个或多个Google Code项目无法直接导入GitLab,因为它们使用Subversion或Mercurialè¿›è¡Œç‰ˆæœ¬æŽ§åˆ¶ï¼Œè€Œä¸æ˜¯Git。"
-msgid "Online IDE integration settings."
-msgstr "在线IDE集æˆè®¾ç½®ã€‚"
-
msgid "Only admins"
msgstr "仅管ç†å‘˜"
@@ -6732,9 +6723,6 @@ msgstr "设定用户登录的æ¡ä»¶ã€‚å¯ç”¨å¼ºåˆ¶åŒé‡è®¤è¯ã€‚"
msgid "Set up CI/CD"
msgstr "é…ç½® CI/CD"
-msgid "Set up Koding"
-msgstr "设置 Koding"
-
msgid "Set up a %{type} Runner manually"
msgstr "手动设置%{type}Runner "
diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po
index 3ecd9fc4cd2..d8fe2d5b13e 100644
--- a/locale/zh_HK/gitlab.po
+++ b/locale/zh_HK/gitlab.po
@@ -4230,12 +4230,6 @@ msgstr ""
msgid "June"
msgstr ""
-msgid "Koding"
-msgstr ""
-
-msgid "Koding Dashboard"
-msgstr ""
-
msgid "Kubernetes"
msgstr ""
@@ -5191,9 +5185,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr ""
-
msgid "Only admins"
msgstr ""
@@ -6732,9 +6723,6 @@ msgstr ""
msgid "Set up CI/CD"
msgstr ""
-msgid "Set up Koding"
-msgstr "設置 Koding"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po
index bb907d9a583..1bccf6de738 100644
--- a/locale/zh_TW/gitlab.po
+++ b/locale/zh_TW/gitlab.po
@@ -4230,12 +4230,6 @@ msgstr "六月"
msgid "June"
msgstr "六月"
-msgid "Koding"
-msgstr "Koding"
-
-msgid "Koding Dashboard"
-msgstr "Koding æŽ§åˆ¶é¢æ¿"
-
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5191,9 +5185,6 @@ msgstr ""
msgid "One or more of your Google Code projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
-msgid "Online IDE integration settings."
-msgstr "線上 IDE æ•´åˆè¨­å®šã€‚"
-
msgid "Only admins"
msgstr ""
@@ -6732,9 +6723,6 @@ msgstr "設定使用者登入的需求。啟用強制性的兩步驟驗證。"
msgid "Set up CI/CD"
msgstr "設定 CI/CD"
-msgid "Set up Koding"
-msgstr "設定 Koding"
-
msgid "Set up a %{type} Runner manually"
msgstr ""
diff --git a/package.json b/package.json
index ac9a73cd2c9..8ec47bc2837 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,7 @@
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/preset-env": "^7.1.0",
- "@gitlab-org/gitlab-svgs": "^1.29.0",
+ "@gitlab-org/gitlab-svgs": "^1.32.0",
"@gitlab-org/gitlab-ui": "^1.8.0",
"autosize": "^4.0.0",
"axios": "^0.17.1",
@@ -111,8 +111,8 @@
"xterm": "^3.5.0"
},
"devDependencies": {
+ "@gitlab/eslint-config": "^1.1.0",
"axios-mock-adapter": "^1.15.0",
- "babel-eslint": "^9.0.0",
"babel-plugin-istanbul": "^5.1.0",
"babel-plugin-rewire": "^1.2.0",
"babel-template": "^6.26.0",
@@ -120,15 +120,10 @@
"chalk": "^2.4.1",
"commander": "^2.18.0",
"eslint": "~5.6.0",
- "eslint-config-airbnb-base": "^13.1.0",
- "eslint-config-prettier": "^3.1.0",
"eslint-import-resolver-webpack": "^0.10.1",
- "eslint-plugin-filenames": "^1.3.2",
"eslint-plugin-html": "4.0.5",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jasmine": "^2.10.1",
- "eslint-plugin-promise": "^4.0.1",
- "eslint-plugin-vue": "^5.0.0-beta.3",
"gettext-extractor": "^3.3.2",
"gettext-extractor-vue": "^4.0.1",
"ignore": "^3.3.7",
diff --git a/qa/Dockerfile b/qa/Dockerfile
index abf2184e1e2..9956ced0ef6 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -3,10 +3,20 @@ LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>"
ENV DEBIAN_FRONTEND noninteractive
##
+# Add support for stretch-backports
+#
+RUN echo "deb http://ftp.debian.org/debian stretch-backports main" >> /etc/apt/sources.list
+
+##
# Update APT sources and install some dependencies
#
RUN sed -i "s/httpredir.debian.org/ftp.us.debian.org/" /etc/apt/sources.list
-RUN apt-get update && apt-get install -y wget git unzip xvfb
+RUN apt-get update && apt-get install -y wget unzip xvfb
+
+##
+# Install some packages from backports
+#
+RUN apt-get -y -t stretch-backports install git git-lfs
##
# Install Docker
diff --git a/qa/qa.rb b/qa/qa.rb
index 4499139d5cd..d6a150fa0b4 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -16,6 +16,8 @@ module QA
autoload :Browser, 'qa/runtime/browser'
autoload :Env, 'qa/runtime/env'
autoload :Address, 'qa/runtime/address'
+ autoload :Path, 'qa/runtime/path'
+ autoload :Fixtures, 'qa/runtime/fixtures'
module API
autoload :Client, 'qa/runtime/api/client'
@@ -47,6 +49,7 @@ module QA
autoload :ProjectImportedFromGithub, 'qa/factory/resource/project_imported_from_github'
autoload :MergeRequestFromFork, 'qa/factory/resource/merge_request_from_fork'
autoload :DeployKey, 'qa/factory/resource/deploy_key'
+ autoload :DeployToken, 'qa/factory/resource/deploy_token'
autoload :Branch, 'qa/factory/resource/branch'
autoload :SecretVariable, 'qa/factory/resource/secret_variable'
autoload :Runner, 'qa/factory/resource/runner'
@@ -95,6 +98,7 @@ module QA
module Integration
autoload :Github, 'qa/scenario/test/integration/github'
autoload :LDAP, 'qa/scenario/test/integration/ldap'
+ autoload :InstanceSAML, 'qa/scenario/test/integration/instance_saml'
autoload :Kubernetes, 'qa/scenario/test/integration/kubernetes'
autoload :Mattermost, 'qa/scenario/test/integration/mattermost'
autoload :ObjectStorage, 'qa/scenario/test/integration/object_storage'
@@ -174,10 +178,12 @@ module QA
autoload :Repository, 'qa/page/project/settings/repository'
autoload :CICD, 'qa/page/project/settings/ci_cd'
autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
+ autoload :DeployTokens, 'qa/page/project/settings/deploy_tokens'
autoload :ProtectedBranches, 'qa/page/project/settings/protected_branches'
autoload :SecretVariables, 'qa/page/project/settings/secret_variables'
autoload :Runners, 'qa/page/project/settings/runners'
autoload :MergeRequest, 'qa/page/project/settings/merge_request'
+ autoload :Members, 'qa/page/project/settings/members'
end
module Issue
@@ -214,6 +220,10 @@ module QA
autoload :New, 'qa/page/project/wiki/new'
autoload :Show, 'qa/page/project/wiki/show'
end
+
+ module WebIDE
+ autoload :Edit, 'qa/page/project/web_ide/edit'
+ end
end
module Profile
@@ -260,6 +270,9 @@ module QA
autoload :Dropzone, 'qa/page/component/dropzone'
autoload :GroupsFilter, 'qa/page/component/groups_filter'
autoload :Select2, 'qa/page/component/select2'
+ autoload :DropdownFilter, 'qa/page/component/dropdown_filter'
+ autoload :UsersSelect, 'qa/page/component/users_select'
+
module Issuable
autoload :Common, 'qa/page/component/issuable/common'
end
@@ -292,6 +305,18 @@ module QA
autoload :Config, 'qa/specs/config'
autoload :Runner, 'qa/specs/runner'
end
+
+ ##
+ # Classes that describe the structure of vendor/third party application pages
+ #
+ module Vendor
+ module SAMLIdp
+ module Page
+ autoload :Base, 'qa/vendor/saml_idp/page/base'
+ autoload :Login, 'qa/vendor/saml_idp/page/login'
+ end
+ end
+ end
end
QA::Runtime::Release.extend_autoloads!
diff --git a/qa/qa/factory/resource/deploy_token.rb b/qa/qa/factory/resource/deploy_token.rb
new file mode 100644
index 00000000000..159f79ac50b
--- /dev/null
+++ b/qa/qa/factory/resource/deploy_token.rb
@@ -0,0 +1,48 @@
+module QA
+ module Factory
+ module Resource
+ class DeployToken < Factory::Base
+ attr_accessor :name, :expires_at
+
+ product :username do |resource|
+ Page::Project::Settings::Repository.act do
+ expand_deploy_tokens do |token|
+ token.token_username
+ end
+ end
+ end
+
+ product :password do |password|
+ Page::Project::Settings::Repository.act do
+ expand_deploy_tokens do |token|
+ token.token_password
+ end
+ end
+ end
+
+ dependency Factory::Resource::Project, as: :project do |project|
+ project.name = 'project-to-deploy'
+ project.description = 'project for adding deploy token test'
+ end
+
+ def fabricate!
+ project.visit!
+
+ Page::Project::Menu.act do
+ click_repository_settings
+ end
+
+ Page::Project::Settings::Repository.perform do |setting|
+ setting.expand_deploy_tokens do |page|
+ page.fill_token_name(name)
+ page.fill_token_expires_at(expires_at)
+ page.fill_scopes(read_repository: true, read_registry: false)
+
+ page.add_token
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/factory/resource/file.rb b/qa/qa/factory/resource/file.rb
index 2016d10ddae..f8dea06d361 100644
--- a/qa/qa/factory/resource/file.rb
+++ b/qa/qa/factory/resource/file.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Factory
module Resource
@@ -19,7 +21,7 @@ module QA
def fabricate!
project.visit!
- Page::Project::Show.act { go_to_new_file! }
+ Page::Project::Show.act { create_new_file! }
Page::File::Form.perform do |page|
page.add_name(@name)
diff --git a/qa/qa/page/README.md b/qa/qa/page/README.md
index dfad460a9a5..4d58f1a43b7 100644
--- a/qa/qa/page/README.md
+++ b/qa/qa/page/README.md
@@ -70,15 +70,15 @@ module Page
module Main
class Login < Page::Base
view 'app/views/devise/passwords/edit.html.haml' do
- element :password_field, 'password_field :password'
- element :password_confirmation, 'password_field :password_confirmation'
- element :change_password_button, 'submit "Change your password"'
+ element :password_field
+ element :password_confirmation
+ element :change_password_button
end
view 'app/views/devise/sessions/_new_base.html.haml' do
- element :login_field, 'text_field :login'
- element :password_field, 'password_field :password'
- element :sign_in_button, 'submit "Sign in"'
+ element :login_field
+ element :password_field
+ element :sign_in_button
end
# ...
@@ -87,23 +87,31 @@ end
```
The `view` DSL method declares the filename of the view where an
-`element` is implmented.
+`element` is implemented.
-The `element` DSL method in turn declares an element and defines a value
-to match to the actual view code. It is possible to use `element` with value,
-with a String value or with a Regexp.
+The `element` DSL method in turn declares an element for which a corresponding
+`qa-element-name-dasherized` CSS class need to be added to the view file.
+
+You can also define a value (String or Regexp) to match to the actual view
+code but **this is deprecated** in favor of the above method for two reasons:
+
+- Consistency: there is only one way to define an element
+- Separation of concerns: QA uses dedicated CSS classes instead of reusing code
+ or classes used by other components (e.g. `js-*` classes etc.)
```ruby
view 'app/views/my/view.html.haml' do
+ # Implicitly require `.qa-logout-button` CSS class to be present in the view
+ element :logout_button
+
+ ## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
# Require `f.submit "Sign in"` to be present in `my/view.html.haml
- element :my_button, 'f.submit "Sign in"'
+ element :my_button, 'f.submit "Sign in"' # rubocop:disable QA/ElementWithPattern
+ ## This is deprecated and forbidden by the `QA/ElementWithPattern` RuboCop cop.
# Match every line in `my/view.html.haml` against
# `/link_to .* "My Profile"/` regexp.
- element :profile_link, /link_to .* "My Profile"/
-
- # Implicitly require `.qa-logout-button` CSS class to be present in the view
- element :logout_button
+ element :profile_link, /link_to .* "My Profile"/ # rubocop:disable QA/ElementWithPattern
end
```
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 142707521df..160ec58cf2c 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'capybara/dsl'
module QA
@@ -7,6 +9,8 @@ module QA
include Scenario::Actable
extend SingleForwardable
+ ElementNotFound = Class.new(RuntimeError)
+
def_delegators :evaluator, :view, :views
def refresh
@@ -28,6 +32,21 @@ module QA
false
end
+ def with_retry(max_attempts: 3, reload: false)
+ attempts = 0
+
+ while attempts < max_attempts
+ result = yield
+ return result if result
+
+ refresh if reload
+
+ attempts += 1
+ end
+
+ false
+ end
+
def scroll_to(selector, text: nil)
page.execute_script <<~JS
var elements = Array.from(document.querySelectorAll('#{selector}'));
diff --git a/qa/qa/page/component/clone_panel.rb b/qa/qa/page/component/clone_panel.rb
index 8e8ff4e3bb0..94e761b0e0c 100644
--- a/qa/qa/page/component/clone_panel.rb
+++ b/qa/qa/page/component/clone_panel.rb
@@ -7,8 +7,8 @@ module QA
def self.included(base)
base.view 'app/views/shared/_clone_panel.html.haml' do
element :clone_dropdown
- element :clone_options_dropdown, '.clone-options-dropdown'
- element :project_repository_location, 'text_field_tag :project_clone'
+ element :clone_options_dropdown, '.clone-options-dropdown' # rubocop:disable QA/ElementWithPattern
+ element :project_repository_location, 'text_field_tag :project_clone' # rubocop:disable QA/ElementWithPattern
end
end
diff --git a/qa/qa/page/component/dropdown_filter.rb b/qa/qa/page/component/dropdown_filter.rb
new file mode 100644
index 00000000000..e896c382779
--- /dev/null
+++ b/qa/qa/page/component/dropdown_filter.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module DropdownFilter
+ def filter_and_select(item)
+ wait(reload: false) do
+ page.has_css?('.dropdown-input-field')
+ end
+
+ find('.dropdown-input-field').set(item)
+ click_link item
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/component/users_select.rb b/qa/qa/page/component/users_select.rb
new file mode 100644
index 00000000000..f88d6450a33
--- /dev/null
+++ b/qa/qa/page/component/users_select.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Component
+ module UsersSelect
+ def select_user(element, username)
+ find("#{element_selector_css(element)} input").set(username)
+ find('.ajax-users-dropdown .user-username', text: "@#{username}").click
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/dashboard/groups.rb b/qa/qa/page/dashboard/groups.rb
index c6ef932e33f..7a07515de62 100644
--- a/qa/qa/page/dashboard/groups.rb
+++ b/qa/qa/page/dashboard/groups.rb
@@ -7,12 +7,12 @@ module QA
include Page::Component::GroupsFilter
view 'app/views/shared/groups/_search_form.html.haml' do
- element :groups_filter, 'search_field_tag :filter'
- element :groups_filter_placeholder, 'Search by name'
+ element :groups_filter, 'search_field_tag :filter' # rubocop:disable QA/ElementWithPattern
+ element :groups_filter_placeholder, 'Search by name' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/dashboard/_groups_head.html.haml' do
- element :new_group_button, 'link_to _("New group")'
+ element :new_group_button, 'link_to _("New group")' # rubocop:disable QA/ElementWithPattern
end
def has_group?(name)
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index 5b2827c089c..0f434577b3b 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -5,7 +5,7 @@ module QA
view 'app/views/dashboard/projects/index.html.haml'
view 'app/views/shared/projects/_search_form.html.haml' do
- element :form_filter_by_name, /form_tag.+id: 'project-filter-form'/
+ element :form_filter_by_name, /form_tag.+id: 'project-filter-form'/ # rubocop:disable QA/ElementWithPattern
end
def go_to_project(name)
diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb
index f6e502f500b..a1534231691 100644
--- a/qa/qa/page/file/form.rb
+++ b/qa/qa/page/file/form.rb
@@ -3,14 +3,23 @@ module QA
module File
class Form < Page::Base
include Shared::CommitMessage
+ include Page::Component::DropdownFilter
view 'app/views/projects/blob/_editor.html.haml' do
- element :file_name, "text_field_tag 'file_name'"
- element :editor, '#editor'
+ element :file_name, "text_field_tag 'file_name'" # rubocop:disable QA/ElementWithPattern
+ element :editor, '#editor' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/projects/_commit_button.html.haml' do
- element :commit_changes, "button_tag 'Commit changes'"
+ element :commit_changes, "button_tag 'Commit changes'" # rubocop:disable QA/ElementWithPattern
+ end
+
+ view 'app/views/projects/blob/_template_selectors.html.haml' do
+ element :template_type_dropdown
+ element :gitignore_dropdown
+ element :gitlab_ci_yml_dropdown
+ element :dockerfile_dropdown
+ element :license_dropdown
end
def add_name(name)
@@ -29,6 +38,25 @@ module QA
click_on 'Commit changes'
end
+ def select_template(template_type, template)
+ click_element :template_type_dropdown
+ click_link template_type
+
+ case template_type
+ when '.gitignore'
+ click_element :gitignore_dropdown
+ when '.gitlab-ci.yml'
+ click_element :gitlab_ci_yml_dropdown
+ when 'Dockerfile'
+ click_element :dockerfile_dropdown
+ when 'LICENSE'
+ click_element :license_dropdown
+ else
+ raise %Q(Unsupported template_type "#{template_type}". Please confirm that it is a valid option.)
+ end
+ filter_and_select template
+ end
+
private
def text_area
diff --git a/qa/qa/page/file/shared/commit_message.rb b/qa/qa/page/file/shared/commit_message.rb
index 5af1a55e2ef..aa1bb081cd3 100644
--- a/qa/qa/page/file/shared/commit_message.rb
+++ b/qa/qa/page/file/shared/commit_message.rb
@@ -5,7 +5,7 @@ module QA
module CommitMessage
def self.included(base)
base.view 'app/views/shared/_commit_message_container.html.haml' do
- element :commit_message, "text_area_tag 'commit_message'"
+ element :commit_message, "text_area_tag 'commit_message'" # rubocop:disable QA/ElementWithPattern
end
end
diff --git a/qa/qa/page/file/show.rb b/qa/qa/page/file/show.rb
index 99f5924b67f..abd8ebb089f 100644
--- a/qa/qa/page/file/show.rb
+++ b/qa/qa/page/file/show.rb
@@ -5,12 +5,12 @@ module QA
include Shared::CommitMessage
view 'app/helpers/blob_helper.rb' do
- element :edit_button, "_('Edit')"
- element :delete_button, /label:\s+"Delete"/
+ element :edit_button, "_('Edit')" # rubocop:disable QA/ElementWithPattern
+ element :delete_button, /label:\s+"Delete"/ # rubocop:disable QA/ElementWithPattern
end
view 'app/views/projects/blob/_remove.html.haml' do
- element :delete_file_button, "button_tag 'Delete file'"
+ element :delete_file_button, "button_tag 'Delete file'" # rubocop:disable QA/ElementWithPattern
end
def click_edit
diff --git a/qa/qa/page/group/new.rb b/qa/qa/page/group/new.rb
index 48b71a7c883..39584daf334 100644
--- a/qa/qa/page/group/new.rb
+++ b/qa/qa/page/group/new.rb
@@ -3,14 +3,14 @@ module QA
module Group
class New < Page::Base
view 'app/views/shared/_group_form.html.haml' do
- element :group_path_field, 'text_field :path'
- element :group_name_field, 'text_field :name'
- element :group_description_field, 'text_area :description'
+ element :group_path_field, 'text_field :path' # rubocop:disable QA/ElementWithPattern
+ element :group_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
+ element :group_description_field, 'text_area :description' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/groups/new.html.haml' do
- element :create_group_button, "submit 'Create group'"
- element :visibility_radios, 'visibility_level:'
+ element :create_group_button, "submit 'Create group'" # rubocop:disable QA/ElementWithPattern
+ element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end
def set_path(path)
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 58a9e861971..0f0ab81a4ef 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -15,7 +15,7 @@ module QA
end
view 'app/assets/javascripts/groups/constants.js' do
- element :no_result_text, 'No groups or projects matched your search'
+ element :no_result_text, 'No groups or projects matched your search' # rubocop:disable QA/ElementWithPattern
end
def go_to_subgroup(name)
diff --git a/qa/qa/page/issuable/sidebar.rb b/qa/qa/page/issuable/sidebar.rb
index f207264e24f..d3751b712c9 100644
--- a/qa/qa/page/issuable/sidebar.rb
+++ b/qa/qa/page/issuable/sidebar.rb
@@ -3,8 +3,8 @@ module QA
module Issuable
class Sidebar < Page::Base
view 'app/views/shared/issuable/_sidebar.html.haml' do
- element :labels_block, ".issuable-show-labels"
- element :milestones_block, '.block.milestone'
+ element :labels_block, ".issuable-show-labels" # rubocop:disable QA/ElementWithPattern
+ element :milestones_block, '.block.milestone' # rubocop:disable QA/ElementWithPattern
end
def has_label?(label)
diff --git a/qa/qa/page/layout/banner.rb b/qa/qa/page/layout/banner.rb
index e7654bdafc9..2223f2adec8 100644
--- a/qa/qa/page/layout/banner.rb
+++ b/qa/qa/page/layout/banner.rb
@@ -3,7 +3,7 @@ module QA
module Layout
class Banner < Page::Base
view 'app/views/layouts/header/_read_only_banner.html.haml' do
- element :flash_notice, ".flash-notice"
+ element :flash_notice, ".flash-notice" # rubocop:disable QA/ElementWithPattern
end
def has_notice?(message)
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index eab7a85ff04..94b9486b0d5 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -31,6 +31,10 @@ module QA
element :register_tab
end
+ view 'app/views/devise/shared/_omniauth_box.html.haml' do
+ element :saml_login_button
+ end
+
def initialize
# The login page is usually the entry point for all the scenarios so
# we need to wait for the instance to start. That said, in some cases
@@ -130,6 +134,11 @@ module QA
click_element :sign_in_button
end
+ def sign_in_with_saml
+ set_initial_password_if_present
+ click_element :saml_login_button
+ end
+
def sign_in_using_gitlab_credentials(user)
switch_to_sign_in_tab if has_sign_in_tab?
switch_to_standard_tab if has_standard_tab?
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index e18b95bde9f..fb45ebef1b6 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -5,14 +5,14 @@ module QA
module Main
class Menu < Page::Base
view 'app/views/layouts/header/_current_user_dropdown.html.haml' do
- element :user_sign_out_link, 'link_to _("Sign out")'
- element :settings_link, 'link_to s_("CurrentUser|Settings")'
+ element :user_sign_out_link, 'link_to _("Sign out")' # rubocop:disable QA/ElementWithPattern
+ element :settings_link, 'link_to s_("CurrentUser|Settings")' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/layouts/header/_default.html.haml' do
element :navbar
element :user_avatar
- element :user_menu, '.dropdown-menu'
+ element :user_menu, '.dropdown-menu' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/layouts/nav/_dashboard.html.haml' do
@@ -68,10 +68,6 @@ module QA
end
end
- def assert_has_personal_area
- raise "Failed to sign in" unless has_personal_area?
- end
-
private
def within_top_menu
diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb
index 618f114e058..bc44d274314 100644
--- a/qa/qa/page/main/oauth.rb
+++ b/qa/qa/page/main/oauth.rb
@@ -3,7 +3,7 @@ module QA
module Main
class OAuth < Page::Base
view 'app/views/doorkeeper/authorizations/new.html.haml' do
- element :authorization_button, 'submit_tag _("Authorize")'
+ element :authorization_button, 'submit_tag _("Authorize")' # rubocop:disable QA/ElementWithPattern
end
def needs_authorization?
diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb
index dddda4f2bdf..9ca498012eb 100644
--- a/qa/qa/page/main/sign_up.rb
+++ b/qa/qa/page/main/sign_up.rb
@@ -1,25 +1,35 @@
+# frozen_string_literal: true
+
module QA
module Page
module Main
class SignUp < Page::Base
view 'app/views/devise/shared/_signup_box.html.haml' do
- element :name, 'text_field :name'
- element :username, 'text_field :username'
- element :email_field, 'email_field :email'
- element :email_confirmation, 'email_field :email_confirmation'
- element :password, 'password_field :password'
- element :register_button, 'submit "Register"'
+ element :new_user_name
+ element :new_user_username
+ element :new_user_email
+ element :new_user_email_confirmation
+ element :new_user_password
+ element :new_user_register_button
+ element :new_user_accept_terms
end
def sign_up!(user)
- fill_in :new_user_name, with: user.name
- fill_in :new_user_username, with: user.username
- fill_in :new_user_email, with: user.email
- fill_in :new_user_email_confirmation, with: user.email
- fill_in :new_user_password, with: user.password
- click_button 'Register'
+ fill_element :new_user_name, user.name
+ fill_element :new_user_username, user.username
+ fill_element :new_user_email, user.email
+ fill_element :new_user_email_confirmation, user.email
+ fill_element :new_user_password, user.password
+
+ check_element :new_user_accept_terms if has_element?(:new_user_accept_terms)
+
+ signed_in = with_retry do
+ click_element :new_user_register_button
+
+ Page::Main::Menu.act { has_personal_area? }
+ end
- Page::Main::Menu.act { assert_has_personal_area }
+ raise "Failed to register and sign in" unless signed_in
end
end
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index d9254d89541..b40e90ef4ad 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -4,19 +4,19 @@ module QA
class Show < Page::Base
view 'app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue' do
element :merge_button
- element :fast_forward_message, 'Fast-forward merge without a merge commit'
+ element :fast_forward_message, 'Fast-forward merge without a merge commit' # rubocop:disable QA/ElementWithPattern
element :merge_moment_dropdown
element :merge_when_pipeline_succeeds_option
element :merge_immediately_option
end
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue' do
- element :merged_status, 'The changes were merged into'
+ element :merged_status, 'The changes were merged into' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue' do
element :mr_rebase_button
- element :no_fast_forward_message, 'Fast-forward merge is not possible'
+ element :no_fast_forward_message, 'Fast-forward merge is not possible' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue' do
diff --git a/qa/qa/page/profile/menu.rb b/qa/qa/page/profile/menu.rb
index f8a7d64e016..2d503499e13 100644
--- a/qa/qa/page/profile/menu.rb
+++ b/qa/qa/page/profile/menu.rb
@@ -5,10 +5,10 @@ module QA
module Profile
class Menu < Page::Base
view 'app/views/layouts/nav/sidebar/_profile.html.haml' do
- element :access_token_link, 'link_to profile_personal_access_tokens_path'
- element :access_token_title, 'Access Tokens'
- element :top_level_items, '.sidebar-top-level-items'
- element :ssh_keys, 'SSH Keys'
+ element :access_token_link, 'link_to profile_personal_access_tokens_path' # rubocop:disable QA/ElementWithPattern
+ element :access_token_title, 'Access Tokens' # rubocop:disable QA/ElementWithPattern
+ element :top_level_items, '.sidebar-top-level-items' # rubocop:disable QA/ElementWithPattern
+ element :ssh_keys, 'SSH Keys' # rubocop:disable QA/ElementWithPattern
end
def click_access_tokens
diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb
index f5ae47dadd0..2f0202951bb 100644
--- a/qa/qa/page/profile/personal_access_tokens.rb
+++ b/qa/qa/page/profile/personal_access_tokens.rb
@@ -3,13 +3,13 @@ module QA
module Profile
class PersonalAccessTokens < Page::Base
view 'app/views/shared/_personal_access_tokens_form.html.haml' do
- element :personal_access_token_name_field, 'text_field :name'
- element :create_token_button, 'submit "Create #{type} token"' # rubocop:disable Lint/InterpolationCheck
- element :scopes_api_radios, "label :scopes"
+ element :personal_access_token_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
+ element :create_token_button, 'submit "Create #{type} token"' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
+ element :scopes_api_radios, "label :scopes" # rubocop:disable QA/ElementWithPattern
end
view 'app/views/profiles/personal_access_tokens/index.html.haml' do
- element :create_token_field, "text_field_tag 'created-personal-access-token'"
+ element :create_token_field, "text_field_tag 'created-personal-access-token'" # rubocop:disable QA/ElementWithPattern
end
def fill_token_name(name)
diff --git a/qa/qa/page/project/activity.rb b/qa/qa/page/project/activity.rb
index a0500b4d31a..56fbaa90790 100644
--- a/qa/qa/page/project/activity.rb
+++ b/qa/qa/page/project/activity.rb
@@ -3,7 +3,7 @@ module QA
module Project
class Activity < Page::Base
view 'app/views/shared/_event_filter.html.haml' do
- element :push_events, "event_filter_link EventFilter::PUSH, _('Push events')"
+ element :push_events, "event_filter_link EventFilter::PUSH, _('Push events')" # rubocop:disable QA/ElementWithPattern
end
def go_to_push_events
diff --git a/qa/qa/page/project/fork/new.rb b/qa/qa/page/project/fork/new.rb
index ed92df956bf..140c004b458 100644
--- a/qa/qa/page/project/fork/new.rb
+++ b/qa/qa/page/project/fork/new.rb
@@ -4,7 +4,7 @@ module QA
module Fork
class New < Page::Base
view 'app/views/projects/forks/_fork_button.html.haml' do
- element :namespace, 'link_to project_forks_path'
+ element :namespace, 'link_to project_forks_path' # rubocop:disable QA/ElementWithPattern
end
def choose_namespace(namespace = Runtime::Namespace.path)
diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb
index 1a410a0f8a5..a3cde73d3f2 100644
--- a/qa/qa/page/project/import/github.rb
+++ b/qa/qa/page/project/import/github.rb
@@ -6,16 +6,16 @@ module QA
include Page::Component::Select2
view 'app/views/import/github/new.html.haml' do
- element :personal_access_token_field, 'text_field_tag :personal_access_token'
- element :list_repos_button, "submit_tag _('List your GitHub repositories')"
+ element :personal_access_token_field, 'text_field_tag :personal_access_token' # rubocop:disable QA/ElementWithPattern
+ element :list_repos_button, "submit_tag _('List your GitHub repositories')" # rubocop:disable QA/ElementWithPattern
end
view 'app/views/import/_githubish_status.html.haml' do
- element :project_import_row, 'data: { qa: { repo_path: repo.full_name } }'
+ element :project_import_row, 'data: { qa: { repo_path: repo.full_name } }' # rubocop:disable QA/ElementWithPattern
element :project_namespace_select
- element :project_namespace_field, 'select_tag :namespace_id'
- element :project_path_field, 'text_field_tag :path, sanitize_project_name(repo.name)'
- element :import_button, "_('Import')"
+ element :project_namespace_field, 'select_tag :namespace_id' # rubocop:disable QA/ElementWithPattern
+ element :project_path_field, 'text_field_tag :path, sanitize_project_name(repo.name)' # rubocop:disable QA/ElementWithPattern
+ element :import_button, "_('Import')" # rubocop:disable QA/ElementWithPattern
end
def add_personal_access_token(personal_access_token)
diff --git a/qa/qa/page/project/issue/index.rb b/qa/qa/page/project/issue/index.rb
index b5903f536a4..1035bf74a43 100644
--- a/qa/qa/page/project/issue/index.rb
+++ b/qa/qa/page/project/issue/index.rb
@@ -4,7 +4,7 @@ module QA
module Issue
class Index < Page::Base
view 'app/views/projects/issues/_issue.html.haml' do
- element :issue_link, 'link_to issue.title'
+ element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern
end
def go_to_issue(title)
diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb
index 7fc581da1ed..03b605ab24b 100644
--- a/qa/qa/page/project/issue/new.rb
+++ b/qa/qa/page/project/issue/new.rb
@@ -4,15 +4,15 @@ module QA
module Issue
class New < Page::Base
view 'app/views/shared/issuable/_form.html.haml' do
- element :submit_issue_button, 'form.submit "Submit'
+ element :submit_issue_button, 'form.submit "Submit' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/shared/issuable/form/_title.html.haml' do
- element :issue_title_textbox, 'form.text_field :title'
+ element :issue_title_textbox, 'form.text_field :title' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/shared/form_elements/_description.html.haml' do
- element :issue_description_textarea, "render 'projects/zen', f: form, attr: :description"
+ element :issue_description_textarea, "render 'projects/zen', f: form, attr: :description" # rubocop:disable QA/ElementWithPattern
end
def add_title(title)
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 9a738f56202..1062f0b2dbb 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -8,17 +8,17 @@ module QA
include Page::Component::Issuable::Common
view 'app/views/projects/issues/show.html.haml' do
- element :issue_details, '.issue-details'
- element :title, '.title'
+ element :issue_details, '.issue-details' # rubocop:disable QA/ElementWithPattern
+ element :title, '.title' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/shared/notes/_form.html.haml' do
- element :new_note_form, 'new-note'
- element :new_note_form, 'attr: :note'
+ element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern
+ element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/shared/notes/_comment_button.html.haml' do
- element :comment_button, '%strong Comment'
+ element :comment_button, '%strong Comment' # rubocop:disable QA/ElementWithPattern
end
def issue_title
diff --git a/qa/qa/page/project/job/show.rb b/qa/qa/page/project/job/show.rb
index 228ffd9d381..5baf6439cfc 100644
--- a/qa/qa/page/project/job/show.rb
+++ b/qa/qa/page/project/job/show.rb
@@ -5,12 +5,12 @@ module QA::Page
PASSED_STATUS = 'passed'.freeze
view 'app/views/shared/builds/_build_output.html.haml' do
- element :build_output, '.js-build-output'
- element :loading_animation, '.js-build-refresh'
+ element :build_output, '.js-build-output' # rubocop:disable QA/ElementWithPattern
+ element :loading_animation, '.js-build-refresh' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/vue_shared/components/ci_badge_link.vue' do
- element :status_badge, 'ci-status'
+ element :status_badge, 'ci-status' # rubocop:disable QA/ElementWithPattern
end
def completed?
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index d9f01c50f19..b32d5ea772b 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -6,25 +6,26 @@ module QA
class Menu < Page::Base
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :settings_item
- element :settings_link, 'link_to edit_project_path'
- element :repository_link, "title: _('Repository')"
+ element :settings_link, 'link_to edit_project_path' # rubocop:disable QA/ElementWithPattern
+ element :repository_link, "title: _('Repository')" # rubocop:disable QA/ElementWithPattern
element :link_pipelines
- element :pipelines_settings_link, "title: _('CI / CD')"
- element :operations_kubernetes_link, "title: _('Kubernetes')"
+ element :link_members_settings
+ element :pipelines_settings_link, "title: _('CI / CD')" # rubocop:disable QA/ElementWithPattern
+ element :operations_kubernetes_link, "title: _('Kubernetes')" # rubocop:disable QA/ElementWithPattern
element :operations_environments_link
- element :issues_link, /link_to.*shortcuts-issues/
- element :issues_link_text, "Issues"
- element :merge_requests_link, /link_to.*shortcuts-merge_requests/
- element :merge_requests_link_text, "Merge Requests"
- element :top_level_items, '.sidebar-top-level-items'
- element :operations_section, "class: 'shortcuts-operations'"
- element :activity_link, "title: _('Activity')"
- element :wiki_link_text, "Wiki"
+ element :issues_link, /link_to.*shortcuts-issues/ # rubocop:disable QA/ElementWithPattern
+ element :issues_link_text, "Issues" # rubocop:disable QA/ElementWithPattern
+ element :merge_requests_link, /link_to.*shortcuts-merge_requests/ # rubocop:disable QA/ElementWithPattern
+ element :merge_requests_link_text, "Merge Requests" # rubocop:disable QA/ElementWithPattern
+ element :top_level_items, '.sidebar-top-level-items' # rubocop:disable QA/ElementWithPattern
+ element :operations_section, "class: 'shortcuts-operations'" # rubocop:disable QA/ElementWithPattern
+ element :activity_link, "title: _('Activity')" # rubocop:disable QA/ElementWithPattern
+ element :wiki_link_text, "Wiki" # rubocop:disable QA/ElementWithPattern
element :milestones_link
end
view 'app/assets/javascripts/fly_out_nav.js' do
- element :fly_out, "classList.add('fly-out-list')"
+ element :fly_out, "classList.add('fly-out-list')" # rubocop:disable QA/ElementWithPattern
end
def click_repository_settings
@@ -51,6 +52,14 @@ module QA
end
end
+ def click_members_settings
+ hover_settings do
+ within_submenu do
+ click_element :link_members_settings
+ end
+ end
+ end
+
def click_operations_kubernetes
hover_operations do
within_submenu do
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 0766c98da6f..6acc413b586 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -5,21 +5,21 @@ module QA
include Page::Component::Select2
view 'app/views/projects/new.html.haml' do
- element :import_project_tab, "Import project"
+ element :import_project_tab, "Import project" # rubocop:disable QA/ElementWithPattern
end
view 'app/views/projects/_new_project_fields.html.haml' do
element :project_namespace_select
- element :project_namespace_field, 'namespaces_options'
- element :project_name, 'text_field :name'
- element :project_path, 'text_field :path'
- element :project_description, 'text_area :description'
- element :project_create_button, "submit 'Create project'"
- element :visibility_radios, 'visibility_level:'
+ element :project_namespace_field, 'namespaces_options' # rubocop:disable QA/ElementWithPattern
+ element :project_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
+ element :project_path, 'text_field :path' # rubocop:disable QA/ElementWithPattern
+ element :project_description, 'text_area :description' # rubocop:disable QA/ElementWithPattern
+ element :project_create_button, "submit 'Create project'" # rubocop:disable QA/ElementWithPattern
+ element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/projects/_import_project_pane.html.haml' do
- element :import_github, "icon('github', text: 'GitHub')"
+ element :import_github, "icon('github', text: 'GitHub')" # rubocop:disable QA/ElementWithPattern
end
def choose_test_namespace
diff --git a/qa/qa/page/project/operations/kubernetes/add.rb b/qa/qa/page/project/operations/kubernetes/add.rb
index 11ebe10fb18..18c16ca6db7 100644
--- a/qa/qa/page/project/operations/kubernetes/add.rb
+++ b/qa/qa/page/project/operations/kubernetes/add.rb
@@ -5,7 +5,7 @@ module QA
module Kubernetes
class Add < Page::Base
view 'app/views/projects/clusters/new.html.haml' do
- element :add_existing_cluster_button, "Add existing cluster"
+ element :add_existing_cluster_button, "Add existing cluster" # rubocop:disable QA/ElementWithPattern
end
def add_existing_cluster
diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb
index 38f8527b9b4..f8e026b4405 100644
--- a/qa/qa/page/project/operations/kubernetes/add_existing.rb
+++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb
@@ -5,11 +5,11 @@ module QA
module Kubernetes
class AddExisting < Page::Base
view 'app/views/projects/clusters/user/_form.html.haml' do
- element :cluster_name, 'text_field :name'
- element :api_url, 'text_field :api_url'
- element :ca_certificate, 'text_area :ca_cert'
- element :token, 'text_field :token'
- element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')"
+ element :cluster_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
+ element :api_url, 'text_field :api_url' # rubocop:disable QA/ElementWithPattern
+ element :ca_certificate, 'text_area :ca_cert' # rubocop:disable QA/ElementWithPattern
+ element :token, 'text_field :token' # rubocop:disable QA/ElementWithPattern
+ element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern
element :rbac_checkbox
end
diff --git a/qa/qa/page/project/operations/kubernetes/index.rb b/qa/qa/page/project/operations/kubernetes/index.rb
index 7261b5645da..312b459ac89 100644
--- a/qa/qa/page/project/operations/kubernetes/index.rb
+++ b/qa/qa/page/project/operations/kubernetes/index.rb
@@ -5,7 +5,7 @@ module QA
module Kubernetes
class Index < Page::Base
view 'app/views/projects/clusters/_empty_state.html.haml' do
- element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')"
+ element :add_kubernetes_cluster_button, "link_to s_('ClusterIntegration|Add Kubernetes cluster')" # rubocop:disable QA/ElementWithPattern
end
def add_kubernetes_cluster
diff --git a/qa/qa/page/project/operations/kubernetes/show.rb b/qa/qa/page/project/operations/kubernetes/show.rb
index e831edeb89e..9e8f9ba79d7 100644
--- a/qa/qa/page/project/operations/kubernetes/show.rb
+++ b/qa/qa/page/project/operations/kubernetes/show.rb
@@ -5,13 +5,13 @@ module QA
module Kubernetes
class Show < Page::Base
view 'app/assets/javascripts/clusters/components/application_row.vue' do
- element :application_row, 'js-cluster-application-row-${this.id}'
- element :install_button, "s__('ClusterIntegration|Install')"
- element :installed_button, "s__('ClusterIntegration|Installed')"
+ element :application_row, 'js-cluster-application-row-${this.id}' # rubocop:disable QA/ElementWithPattern
+ element :install_button, "s__('ClusterIntegration|Install')" # rubocop:disable QA/ElementWithPattern
+ element :installed_button, "s__('ClusterIntegration|Installed')" # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/clusters/components/applications.vue' do
- element :ingress_ip_address, 'id="ingress-ip-address"'
+ element :ingress_ip_address, 'id="ingress-ip-address"' # rubocop:disable QA/ElementWithPattern
end
def install!(application_name)
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
index ce430a2a6ee..19d83ecc4f4 100644
--- a/qa/qa/page/project/pipeline/index.rb
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -2,7 +2,7 @@ module QA::Page
module Project::Pipeline
class Index < QA::Page::Base
view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do
- element :pipeline_link, 'class="js-pipeline-url-link"'
+ element :pipeline_link, 'class="js-pipeline-url-link"' # rubocop:disable QA/ElementWithPattern
end
def go_to_latest_pipeline
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
index babc0079f3f..06df1238738 100644
--- a/qa/qa/page/project/pipeline/show.rb
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -2,20 +2,20 @@ module QA::Page
module Project::Pipeline
class Show < QA::Page::Base
view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do
- element :pipeline_header, /header class.*ci-header-container.*/
+ element :pipeline_header, /header class.*ci-header-container.*/ # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/pipelines/components/graph/graph_component.vue' do
- element :pipeline_graph, /class.*pipeline-graph.*/
+ element :pipeline_graph, /class.*pipeline-graph.*/ # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/pipelines/components/graph/job_component.vue' do
- element :job_component, /class.*ci-job-component.*/
- element :job_link, /class.*js-pipeline-graph-job-link.*/
+ element :job_component, /class.*ci-job-component.*/ # rubocop:disable QA/ElementWithPattern
+ element :job_link, /class.*js-pipeline-graph-job-link.*/ # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do
- element :status_icon, 'ci-status-icon-${status}'
+ element :status_icon, 'ci-status-icon-${status}' # rubocop:disable QA/ElementWithPattern
end
def running?
diff --git a/qa/qa/page/project/settings/advanced.rb b/qa/qa/page/project/settings/advanced.rb
index d7b2b66b587..578f097e2dc 100644
--- a/qa/qa/page/project/settings/advanced.rb
+++ b/qa/qa/page/project/settings/advanced.rb
@@ -4,9 +4,9 @@ module QA
module Settings
class Advanced < Page::Base
view 'app/views/projects/edit.html.haml' do
- element :project_path_field, 'text_field :path'
- element :project_name_field, 'text_field :name'
- element :rename_project_button, "submit 'Rename project'"
+ element :project_path_field, 'text_field :path' # rubocop:disable QA/ElementWithPattern
+ element :project_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
+ element :rename_project_button, "submit 'Rename project'" # rubocop:disable QA/ElementWithPattern
end
def rename_to(path)
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index 752d3d93407..cc5fc370a5a 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -12,11 +12,11 @@ module QA # rubocop:disable Naming/FileName
end
view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do
- element :enable_auto_devops_field, 'check_box :enabled'
- element :domain_field, 'text_field :domain'
- element :enable_auto_devops_button, "%strong= s_('CICD|Default to Auto DevOps pipeline')"
- element :domain_input, "%strong= _('Domain')"
- element :save_changes_button, "submit _('Save changes')"
+ element :enable_auto_devops_field, 'check_box :enabled' # rubocop:disable QA/ElementWithPattern
+ element :domain_field, 'text_field :domain' # rubocop:disable QA/ElementWithPattern
+ element :enable_auto_devops_button, "%strong= s_('CICD|Default to Auto DevOps pipeline')" # rubocop:disable QA/ElementWithPattern
+ element :domain_input, "%strong= _('Domain')" # rubocop:disable QA/ElementWithPattern
+ element :save_changes_button, "submit _('Save changes')" # rubocop:disable QA/ElementWithPattern
end
def expand_runners_settings(&block)
diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb
index 874fb381554..f3b217677f2 100644
--- a/qa/qa/page/project/settings/common.rb
+++ b/qa/qa/page/project/settings/common.rb
@@ -8,7 +8,7 @@ module QA
def self.included(base)
base.class_eval do
view 'app/views/projects/edit.html.haml' do
- element :advanced_settings_expand, "= expanded ? 'Collapse' : 'Expand'"
+ element :advanced_settings_expand, "= expanded ? 'Collapse' : 'Expand'" # rubocop:disable QA/ElementWithPattern
end
end
end
diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb
index 90a0e7092bd..3c8c0cbdf7c 100644
--- a/qa/qa/page/project/settings/deploy_keys.rb
+++ b/qa/qa/page/project/settings/deploy_keys.rb
@@ -4,18 +4,18 @@ module QA
module Settings
class DeployKeys < Page::Base
view 'app/views/projects/deploy_keys/_form.html.haml' do
- element :deploy_key_title, 'text_field :title'
- element :deploy_key_key, 'text_area :key'
+ element :deploy_key_title, 'text_field :title' # rubocop:disable QA/ElementWithPattern
+ element :deploy_key_key, 'text_area :key' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/deploy_keys/components/app.vue' do
- element :deploy_keys_section, /class=".*deploy\-keys.*"/
- element :project_deploy_keys, 'class="qa-project-deploy-keys"'
+ element :deploy_keys_section, /class=".*deploy\-keys.*"/ # rubocop:disable QA/ElementWithPattern
+ element :project_deploy_keys, 'class="qa-project-deploy-keys"' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/deploy_keys/components/key.vue' do
- element :key_title, /class=".*qa-key-title.*"/
- element :key_fingerprint, /class=".*qa-key-fingerprint.*"/
+ element :key_title, /class=".*qa-key-title.*"/ # rubocop:disable QA/ElementWithPattern
+ element :key_fingerprint, /class=".*qa-key-fingerprint.*"/ # rubocop:disable QA/ElementWithPattern
end
def fill_key_title(title)
diff --git a/qa/qa/page/project/settings/deploy_tokens.rb b/qa/qa/page/project/settings/deploy_tokens.rb
new file mode 100644
index 00000000000..2d42372cbc5
--- /dev/null
+++ b/qa/qa/page/project/settings/deploy_tokens.rb
@@ -0,0 +1,64 @@
+module QA
+ module Page
+ module Project
+ module Settings
+ class DeployTokens < Page::Base
+ view 'app/views/projects/deploy_tokens/_form.html.haml' do
+ element :deploy_token_name
+ element :deploy_token_expires_at
+ element :deploy_token_read_repository
+ element :deploy_token_read_registry
+ element :create_deploy_token
+ end
+
+ view 'app/views/projects/deploy_tokens/_new_deploy_token.html.haml' do
+ element :created_deploy_token_section
+ element :deploy_token_user
+ element :deploy_token
+ end
+
+ def fill_token_name(name)
+ fill_element :deploy_token_name, name
+ end
+
+ def fill_token_expires_at(expires_at)
+ fill_element :deploy_token_expires_at, expires_at.to_s + "\n"
+ end
+
+ def fill_scopes(read_repository:, read_registry:)
+ check_element :deploy_token_read_repository if read_repository
+ check_element :deploy_token_read_registry if read_registry
+ end
+
+ def add_token
+ click_element :create_deploy_token
+ end
+
+ def token_username
+ within_new_project_deploy_token do
+ find_element(:deploy_token_user).value
+ end
+ end
+
+ def token_password
+ within_new_project_deploy_token do
+ find_element(:deploy_token).value
+ end
+ end
+
+ private
+
+ def within_new_project_deploy_token
+ wait(reload: false) do
+ has_css?(element_selector_css(:created_deploy_token_section))
+ end
+
+ within_element(:created_deploy_token_section) do
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/members.rb b/qa/qa/page/project/settings/members.rb
new file mode 100644
index 00000000000..7fed93ca83f
--- /dev/null
+++ b/qa/qa/page/project/settings/members.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Settings
+ class Members < Page::Base
+ include Page::Component::UsersSelect
+
+ view 'app/views/projects/project_members/_new_project_member.html.haml' do
+ element :member_select_input
+ element :add_member_button
+ end
+
+ view 'app/views/projects/project_members/_team.html.haml' do
+ element :members_list
+ end
+
+ def add_member(username)
+ select_user :member_select_input, username
+ click_element :add_member_button
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/repository.rb b/qa/qa/page/project/settings/repository.rb
index 1ed5f455a85..53ebe28970b 100644
--- a/qa/qa/page/project/settings/repository.rb
+++ b/qa/qa/page/project/settings/repository.rb
@@ -24,6 +24,12 @@ module QA
ProtectedBranches.perform(&block)
end
end
+
+ def expand_deploy_tokens(&block)
+ expand_section(:deploy_tokens_settings) do
+ DeployTokens.perform(&block)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb
index b41668c94cd..ac930f5385a 100644
--- a/qa/qa/page/project/settings/runners.rb
+++ b/qa/qa/page/project/settings/runners.rb
@@ -4,8 +4,8 @@ module QA
module Settings
class Runners < Page::Base
view 'app/views/ci/runner/_how_to_setup_runner.html.haml' do
- element :registration_token, '%code#registration_token'
- element :coordinator_address, '%code#coordinator_address'
+ element :registration_token, '%code#registration_token' # rubocop:disable QA/ElementWithPattern
+ element :coordinator_address, '%code#coordinator_address' # rubocop:disable QA/ElementWithPattern
end
##
@@ -13,7 +13,7 @@ module QA
#
view 'app/helpers/runners_helper.rb' do
# rubocop:disable Lint/InterpolationCheck
- element :runner_status, 'runner-status-#{status}'
+ element :runner_status, 'runner-status-#{status}' # rubocop:disable QA/ElementWithPattern
# rubocop:enable Lint/InterpolationCheck
end
diff --git a/qa/qa/page/project/settings/secret_variables.rb b/qa/qa/page/project/settings/secret_variables.rb
index 937ae6797c8..6a87ef472e4 100644
--- a/qa/qa/page/project/settings/secret_variables.rb
+++ b/qa/qa/page/project/settings/secret_variables.rb
@@ -6,14 +6,14 @@ module QA
include Common
view 'app/views/ci/variables/_variable_row.html.haml' do
- element :variable_row, '.ci-variable-row-body'
- element :variable_key, '.qa-ci-variable-input-key'
- element :variable_value, '.qa-ci-variable-input-value'
+ element :variable_row, '.ci-variable-row-body' # rubocop:disable QA/ElementWithPattern
+ element :variable_key, '.qa-ci-variable-input-key' # rubocop:disable QA/ElementWithPattern
+ element :variable_value, '.qa-ci-variable-input-value' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/ci/variables/_index.html.haml' do
- element :save_variables, '.js-secret-variables-save-button'
- element :reveal_values, '.js-secret-value-reveal-button'
+ element :save_variables, '.js-secret-variables-save-button' # rubocop:disable QA/ElementWithPattern
+ element :reveal_values, '.js-secret-value-reveal-button' # rubocop:disable QA/ElementWithPattern
end
def fill_variable(key, value)
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index 267e7bbc249..fcc4bb79c10 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QA
module Page
module Project
@@ -14,7 +16,7 @@ module QA
view 'app/views/layouts/header/_new_dropdown.haml' do
element :new_menu_toggle
- element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)"
+ element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern
end
view 'app/views/shared/_ref_switcher.html.haml' do
@@ -23,24 +25,30 @@ module QA
end
view 'app/views/projects/buttons/_fork.html.haml' do
- element :fork_label, "%span= s_('ProjectOverview|Fork')"
- element :fork_link, "link_to new_project_fork_path(@project)"
+ element :fork_label, "%span= s_('ProjectOverview|Fork')" # rubocop:disable QA/ElementWithPattern
+ element :fork_link, "link_to new_project_fork_path(@project)" # rubocop:disable QA/ElementWithPattern
end
view 'app/views/projects/_files.html.haml' do
- element :tree_holder, '.tree-holder'
+ element :tree_holder, '.tree-holder' # rubocop:disable QA/ElementWithPattern
+ end
+
+ view 'app/views/projects/buttons/_dropdown.html.haml' do
+ element :create_new_dropdown
+ element :new_file_option
end
- view 'app/presenters/project_presenter.rb' do
- element :new_file_button, "_('New file'),"
+ view 'app/views/projects/tree/_tree_header.html.haml' do
+ element :web_ide_button
end
def project_name
find('.qa-project-name').text
end
- def go_to_new_file!
- click_on 'New file'
+ def create_new_file!
+ click_element :create_new_dropdown
+ click_element :new_file_option
end
def switch_to_branch(branch_name)
@@ -78,6 +86,10 @@ module QA
def fork_project
click_on 'Fork'
end
+
+ def open_web_ide!
+ click_element :web_ide_button
+ end
end
end
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
new file mode 100644
index 00000000000..23e580b81b6
--- /dev/null
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module WebIDE
+ class Edit < Page::Base
+ include Page::Component::DropdownFilter
+
+ view 'app/assets/javascripts/ide/components/ide_tree.vue' do
+ element :new_file
+ end
+
+ view 'app/assets/javascripts/ide/components/ide_tree_list.vue' do
+ element :file_list
+ end
+
+ view 'app/assets/javascripts/ide/components/new_dropdown/modal.vue' do
+ element :full_file_path
+ element :template_list
+ end
+
+ view 'app/assets/javascripts/ide/components/file_templates/bar.vue' do
+ element :file_templates_bar
+ element :file_template_dropdown
+ end
+
+ view 'app/assets/javascripts/ide/components/file_templates/dropdown.vue' do
+ element :dropdown_filter_input
+ end
+
+ view 'app/assets/javascripts/ide/components/commit_sidebar/form.vue' do
+ element :begin_commit_button
+ element :commit_button
+ end
+
+ def has_file?(file_name)
+ within_element(:file_list) do
+ page.has_content? file_name
+ end
+ end
+
+ def create_new_file_from_template(file_name, template)
+ click_element :new_file
+ within_element(:template_list) do
+ begin
+ click_on file_name
+ rescue Capybara::ElementNotFound
+ raise ElementNotFound, %Q(Couldn't find file template named "#{file_name}". Please confirm that it is a valid option.)
+ end
+ end
+
+ wait(reload: false) do
+ within_element(:file_templates_bar) do
+ click_element :file_template_dropdown
+ fill_element :dropdown_filter_input, template
+
+ begin
+ click_on template
+ rescue Capybara::ElementNotFound
+ raise ElementNotFound, %Q(Couldn't find template "#{template}" for #{file_name}. Please confirm that it exists in the list of templates.)
+ end
+ end
+ end
+ end
+
+ def commit_changes
+ click_element :begin_commit_button
+ click_element :commit_button
+
+ wait(reload: false) do
+ page.has_content?('Your changes have been committed')
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/wiki/edit.rb b/qa/qa/page/project/wiki/edit.rb
index 6fa45569cc0..8d0eafa1818 100644
--- a/qa/qa/page/project/wiki/edit.rb
+++ b/qa/qa/page/project/wiki/edit.rb
@@ -4,9 +4,9 @@ module QA
module Wiki
class Edit < Page::Base
view 'app/views/projects/wikis/_main_links.html.haml' do
- element :new_page_link, 'New page'
- element :page_history_link, 'Page history'
- element :edit_page_link, 'Edit'
+ element :new_page_link, 'New page' # rubocop:disable QA/ElementWithPattern
+ element :page_history_link, 'Page history' # rubocop:disable QA/ElementWithPattern
+ element :edit_page_link, 'Edit' # rubocop:disable QA/ElementWithPattern
end
def go_to_new_page
diff --git a/qa/qa/page/project/wiki/new.rb b/qa/qa/page/project/wiki/new.rb
index 415b3835538..2498af8600c 100644
--- a/qa/qa/page/project/wiki/new.rb
+++ b/qa/qa/page/project/wiki/new.rb
@@ -4,15 +4,15 @@ module QA
module Wiki
class New < Page::Base
view 'app/views/projects/wikis/_form.html.haml' do
- element :wiki_title_textbox, 'text_field :title'
- element :wiki_content_textarea, "render 'projects/zen', f: f, attr: :content"
- element :wiki_message_textbox, 'text_field :message'
- element :save_changes_button, 'submit _("Save changes")'
- element :create_page_button, 'submit s_("Wiki|Create page")'
+ element :wiki_title_textbox, 'text_field :title' # rubocop:disable QA/ElementWithPattern
+ element :wiki_content_textarea, "render 'projects/zen', f: f, attr: :content" # rubocop:disable QA/ElementWithPattern
+ element :wiki_message_textbox, 'text_field :message' # rubocop:disable QA/ElementWithPattern
+ element :save_changes_button, 'submit _("Save changes")' # rubocop:disable QA/ElementWithPattern
+ element :create_page_button, 'submit s_("Wiki|Create page")' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/shared/empty_states/_wikis.html.haml' do
- element :create_link, 'Create your first page'
+ element :create_link, 'Create your first page' # rubocop:disable QA/ElementWithPattern
end
def go_to_create_first_page
diff --git a/qa/qa/page/project/wiki/show.rb b/qa/qa/page/project/wiki/show.rb
index c47a715687f..a7c4455d080 100644
--- a/qa/qa/page/project/wiki/show.rb
+++ b/qa/qa/page/project/wiki/show.rb
@@ -8,7 +8,7 @@ module QA
include Page::Component::ClonePanel
view 'app/views/projects/wikis/pages.html.haml' do
- element :clone_repository_link, 'Clone repository'
+ element :clone_repository_link, 'Clone repository' # rubocop:disable QA/ElementWithPattern
end
def go_to_clone_repository
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index 4c64270ce92..9aaf57e8d83 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -51,6 +51,10 @@ module QA
}
)
+ if QA::Runtime::Env.accept_insecure_certs?
+ capabilities['acceptInsecureCerts'] = true
+ end
+
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument("window-size=1240,1680")
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 5bebb5ccec0..4a2109799fa 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -8,6 +8,10 @@ module QA
enabled?(ENV['CHROME_HEADLESS'])
end
+ def accept_insecure_certs?
+ enabled?(ENV['ACCEPT_INSECURE_CERTS'])
+ end
+
def running_in_ci?
ENV['CI'] || ENV['CI_SERVER']
end
diff --git a/qa/qa/runtime/fixtures.rb b/qa/qa/runtime/fixtures.rb
new file mode 100644
index 00000000000..72004d5b00a
--- /dev/null
+++ b/qa/qa/runtime/fixtures.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module QA
+ module Runtime
+ module Fixtures
+ def fetch_template_from_api(api_path, key)
+ request = Runtime::API::Request.new(api_client, "/templates/#{api_path}/#{key}")
+ get request.url
+ json_body[:content]
+ end
+
+ private
+
+ def api_client
+ @api_client ||= Runtime::API::Client.new(:gitlab)
+ end
+ end
+ end
+end
diff --git a/qa/qa/runtime/path.rb b/qa/qa/runtime/path.rb
new file mode 100644
index 00000000000..3169c5dd743
--- /dev/null
+++ b/qa/qa/runtime/path.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ module Runtime
+ module Path
+ extend self
+
+ def qa_root
+ ::File.expand_path('../../', __dir__)
+ end
+ end
+ end
+end
diff --git a/qa/qa/scenario/test/integration/instance_saml.rb b/qa/qa/scenario/test/integration/instance_saml.rb
new file mode 100644
index 00000000000..0697d0c2a0e
--- /dev/null
+++ b/qa/qa/scenario/test/integration/instance_saml.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module Test
+ module Integration
+ class InstanceSAML < Test::Instance::All
+ tags :instance_saml
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
new file mode 100644
index 00000000000..8d5055aab45
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module QA
+ context :manage, :orchestrated, :instance_saml do
+ describe 'Instance wide SAML SSO' do
+ it 'User logs in to gitlab with SAML SSO' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+
+ Page::Main::Login.act { sign_in_with_saml }
+
+ Vendor::SAMLIdp::Page::Login.act { login }
+
+ expect(page).to have_content('Welcome to GitLab')
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
new file mode 100644
index 00000000000..b276c7ee579
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module QA
+ context :manage do
+ describe 'Add project member' do
+ it 'user adds project member' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+
+ user = Factory::Resource::User.fabricate!
+
+ Page::Main::Menu.perform { |main| main.sign_out }
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ Factory::Resource::Project.fabricate! do |resource|
+ resource.name = 'add-member-project'
+ end
+
+ Page::Project::Menu.act { click_members_settings }
+ Page::Project::Settings::Members.perform do |page|
+ page.add_member(user.username)
+ end
+
+ expect(page).to have_content("#{user.name} @#{user.username} Given access")
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
new file mode 100644
index 00000000000..c7edcf4c025
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module QA
+ context :create do
+ describe 'File templates' do
+ include Runtime::Fixtures
+
+ def login
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+ end
+
+ before(:all) do
+ login
+
+ @project = Factory::Resource::Project.fabricate! do |project|
+ project.name = 'file-template-project'
+ project.description = 'Add file templates via the Files view'
+ end
+
+ Page::Main::Menu.act { sign_out }
+ end
+
+ templates = [
+ {
+ file_name: '.gitignore',
+ name: 'Android',
+ api_path: 'gitignores',
+ api_key: 'Android'
+ },
+ {
+ file_name: '.gitlab-ci.yml',
+ name: 'Julia',
+ api_path: 'gitlab_ci_ymls',
+ api_key: 'Julia'
+ },
+ {
+ file_name: 'Dockerfile',
+ name: 'Python',
+ api_path: 'dockerfiles',
+ api_key: 'Python'
+ },
+ {
+ file_name: 'LICENSE',
+ name: 'Mozilla Public License 2.0',
+ api_path: 'licenses',
+ api_key: 'mpl-2.0'
+ }
+ ]
+
+ templates.each do |template|
+ it "user adds #{template[:file_name]} via file template #{template[:name]}" do
+ content = fetch_template_from_api(template[:api_path], template[:api_key])
+
+ login
+ @project.visit!
+
+ Page::Project::Show.act { create_new_file! }
+ Page::File::Form.perform do |page|
+ page.select_template template[:file_name], template[:name]
+ end
+
+ expect(page).to have_content('Template applied')
+ expect(page).to have_button('Undo')
+ expect(page).to have_content(content[0..100])
+
+ Page::File::Form.perform(&:commit_changes)
+
+ expect(page).to have_content('The file has been successfully created.')
+ expect(page).to have_content(template[:file_name])
+ expect(page).to have_content('Add new file')
+ expect(page).to have_content(content[0..100])
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
new file mode 100644
index 00000000000..ab5d97d5b66
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+module QA
+ context :create do
+ describe 'Web IDE file templates' do
+ include Runtime::Fixtures
+
+ def login
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+ end
+
+ before(:all) do
+ login
+
+ @project = Factory::Resource::Project.fabricate! do |project|
+ project.name = 'file-template-project'
+ project.description = 'Add file templates via the Web IDE'
+ end
+
+ # Add a file via the regular Files view because the Web IDE isn't
+ # available unless there is a file present
+ Page::Project::Show.act { create_new_file! }
+ Page::File::Form.perform do |page|
+ page.add_name('dummy')
+ page.add_content('Enable the Web IDE')
+ page.commit_changes
+ end
+
+ Page::Main::Menu.act { sign_out }
+ end
+
+ templates = [
+ {
+ file_name: '.gitignore',
+ name: 'Android',
+ api_path: 'gitignores',
+ api_key: 'Android'
+ },
+ {
+ file_name: '.gitlab-ci.yml',
+ name: 'Julia',
+ api_path: 'gitlab_ci_ymls',
+ api_key: 'Julia'
+ },
+ {
+ file_name: 'Dockerfile',
+ name: 'Python',
+ api_path: 'dockerfiles',
+ api_key: 'Python'
+ },
+ {
+ file_name: 'LICENSE',
+ name: 'Mozilla Public License 2.0',
+ api_path: 'licenses',
+ api_key: 'mpl-2.0'
+ }
+ ]
+
+ templates.each do |template|
+ it "user adds #{template[:file_name]} via file template #{template[:name]}" do
+ content = fetch_template_from_api(template[:api_path], template[:api_key])
+
+ login
+ @project.visit!
+
+ Page::Project::Show.act { open_web_ide! }
+ Page::Project::WebIDE::Edit.perform do |page|
+ page.create_new_file_from_template template[:file_name], template[:name]
+
+ expect(page.has_file?(template[:file_name])).to be_truthy
+ end
+
+ expect(page).to have_button('Undo')
+ expect(page).to have_content(content[0..100])
+
+ Page::Project::WebIDE::Edit.perform do |page|
+ page.commit_changes
+ end
+
+ expect(page).to have_content(template[:file_name])
+ expect(page).to have_content(content[0..100])
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
new file mode 100644
index 00000000000..e521597e07f
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ context :release do
+ describe 'Deploy token creation' do
+ it 'user adds a deploy token' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ deploy_token_name = 'deploy token name'
+ deploy_token_expires_at = Date.today + 7 # 1 Week from now
+
+ deploy_token = Factory::Resource::DeployToken.fabricate! do |resource|
+ resource.name = deploy_token_name
+ resource.expires_at = deploy_token_expires_at
+ end
+
+ expect(deploy_token.username.length).to be > 0
+ expect(deploy_token.password.length).to be > 0
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 785897f4a97..3735bc00aff 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -66,7 +66,7 @@ module QA
expect(pipeline).to have_build('production', status: :success, wait: 1200)
end
- Page::Menu::Side.act { click_operations_environments }
+ Page::Project::Menu.act { click_operations_environments }
Page::Project::Operations::Environments::Index.perform do |index|
index.go_to_environment('production')
end
diff --git a/qa/qa/vendor/saml_idp/page/base.rb b/qa/qa/vendor/saml_idp/page/base.rb
new file mode 100644
index 00000000000..286cb0a8cd8
--- /dev/null
+++ b/qa/qa/vendor/saml_idp/page/base.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module QA
+ module Vendor
+ module SAMLIdp
+ module Page
+ class Base
+ include Capybara::DSL
+ include Scenario::Actable
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/vendor/saml_idp/page/login.rb b/qa/qa/vendor/saml_idp/page/login.rb
new file mode 100644
index 00000000000..9c1f9904a7a
--- /dev/null
+++ b/qa/qa/vendor/saml_idp/page/login.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'capybara/dsl'
+
+module QA
+ module Vendor
+ module SAMLIdp
+ module Page
+ class Login < Page::Base
+ def login
+ fill_in 'username', with: 'user1'
+ fill_in 'password', with: 'user1pass'
+ click_on 'Login'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb
index 52daa9697ee..076a8087db5 100644
--- a/qa/spec/page/base_spec.rb
+++ b/qa/spec/page/base_spec.rb
@@ -9,12 +9,12 @@ describe QA::Page::Base do
subject do
Class.new(described_class) do
view 'path/to/some/view.html.haml' do
- element :something, 'string pattern'
- element :something_else, /regexp pattern/
+ element :something, 'string pattern' # rubocop:disable QA/ElementWithPattern
+ element :something_else, /regexp pattern/ # rubocop:disable QA/ElementWithPattern
end
view 'path/to/some/_partial.html.haml' do
- element :another_element, 'string pattern'
+ element :another_element, 'string pattern' # rubocop:disable QA/ElementWithPattern
end
end
end
diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb
index 34d2ff11447..d7b3ccd316d 100644
--- a/qa/spec/page/view_spec.rb
+++ b/qa/spec/page/view_spec.rb
@@ -8,8 +8,8 @@ describe QA::Page::View do
describe '.evaluate' do
it 'evaluates a block and returns a DSL object' do
results = described_class.evaluate do
- element :something, 'my pattern'
- element :something_else, /another pattern/
+ element :something
+ element :something_else
end
expect(results.elements.size).to eq 2
diff --git a/qa/spec/scenario/test/integration/instance_saml_spec.rb b/qa/spec/scenario/test/integration/instance_saml_spec.rb
new file mode 100644
index 00000000000..cb8a6a630cc
--- /dev/null
+++ b/qa/spec/scenario/test/integration/instance_saml_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+describe QA::Scenario::Test::Integration::InstanceSAML do
+ context '#perform' do
+ it_behaves_like 'a QA scenario class' do
+ let(:tags) { [:instance_saml] }
+ end
+ end
+end
diff --git a/rubocop/cop/qa/element_with_pattern.rb b/rubocop/cop/qa/element_with_pattern.rb
new file mode 100644
index 00000000000..9d80946f1ba
--- /dev/null
+++ b/rubocop/cop/qa/element_with_pattern.rb
@@ -0,0 +1,39 @@
+require_relative '../../qa_helpers'
+
+module RuboCop
+ module Cop
+ module QA
+ # This cop checks for the usage of factories in migration specs
+ #
+ # @example
+ #
+ # # bad
+ # let(:user) { create(:user) }
+ #
+ # # good
+ # let(:users) { table(:users) }
+ # let(:user) { users.create!(name: 'User 1', username: 'user1') }
+ class ElementWithPattern < RuboCop::Cop::Cop
+ include QAHelpers
+
+ MESSAGE = "Don't use a pattern for element, create a corresponding `%s` instead.".freeze
+
+ def on_send(node)
+ return unless in_qa_file?(node)
+ return unless method_name(node).to_s == 'element'
+
+ element_name, pattern = node.arguments
+ return unless pattern
+
+ add_offense(node, location: pattern.source_range, message: MESSAGE % "qa-#{element_name.value.to_s.tr('_', '-')}")
+ end
+
+ private
+
+ def method_name(node)
+ node.children[1]
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/qa_helpers.rb b/rubocop/qa_helpers.rb
new file mode 100644
index 00000000000..f4adf7f4e9f
--- /dev/null
+++ b/rubocop/qa_helpers.rb
@@ -0,0 +1,11 @@
+module RuboCop
+ # Module containing helper methods for writing QA cops.
+ module QAHelpers
+ # Returns true if the given node originated from the qa/ directory.
+ def in_qa_file?(node)
+ path = node.location.expression.source_buffer.name
+
+ path.start_with?(File.join(Dir.pwd, 'qa'))
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 76d6037706e..6c9b8753c1a 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -29,6 +29,7 @@ require_relative 'cop/migration/update_large_table'
require_relative 'cop/project_path_helper'
require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/factories_in_migration_specs'
+require_relative 'cop/qa/element_with_pattern'
require_relative 'cop/sidekiq_options_queue'
require_relative 'cop/destroy_all'
require_relative 'cop/ruby_interpolation_in_translation'
diff --git a/scripts/trigger-build b/scripts/trigger-build
index 4534fcadebf..b76cd5dd6f0 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -68,11 +68,11 @@ module Trigger
def base_variables
{
- 'TOP_UPSTREAM_TRIGGER_PROJECT' => ENV['TOP_UPSTREAM_TRIGGER_PROJECT'] || ENV['CI_PROJECT_PATH'],
- 'UPSTREAM_TRIGGER_PROJECT' => ENV['CI_PROJECT_PATH'],
- 'UPSTREAM_TRIGGER_SOURCE' => ENV['TRIGGER_SOURCE'],
'TRIGGERED_USER' => ENV['TRIGGERED_USER'] || ENV['GITLAB_USER_NAME'],
- 'TRIGGER_SOURCE' => ENV['CI_JOB_URL']
+ 'TRIGGER_SOURCE' => ENV['CI_JOB_URL'],
+ 'TOP_UPSTREAM_SOURCE_PROJECT' => ENV['CI_PROJECT_PATH'],
+ 'TOP_UPSTREAM_SOURCE_JOB' => ENV['CI_JOB_URL'],
+ 'TOP_UPSTREAM_SOURCE_SHA' => ENV['CI_COMMIT_SHA']
}
end
diff --git a/spec/bin/storage_check_spec.rb b/spec/bin/storage_check_spec.rb
deleted file mode 100644
index 02f6fcb6e3a..00000000000
--- a/spec/bin/storage_check_spec.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-require 'spec_helper'
-
-describe 'bin/storage_check' do
- it 'is executable' do
- command = %w[bin/storage_check -t unix://the/path/to/a/unix-socket.sock -i 10 -d]
- expected_output = 'Checking unix://the/path/to/a/unix-socket.sock every 10 seconds'
-
- output, status = Gitlab::Popen.popen(command, Rails.root.to_s)
-
- expect(status).to eq(0)
- expect(output).to include(expected_output)
- end
-end
diff --git a/spec/controllers/admin/health_check_controller_spec.rb b/spec/controllers/admin/health_check_controller_spec.rb
index d15ee0021d9..e13401fc06b 100644
--- a/spec/controllers/admin/health_check_controller_spec.rb
+++ b/spec/controllers/admin/health_check_controller_spec.rb
@@ -8,18 +8,10 @@ describe Admin::HealthCheckController do
end
describe 'GET show' do
- it 'loads the git storage health information' do
+ it 'loads the health information' do
get :show
- expect(assigns[:failing_storage_statuses]).not_to be_nil
- end
- end
-
- describe 'POST reset_storage_health' do
- it 'resets all storage health information' do
- expect(Gitlab::Git::Storage::FailureInfo).to receive(:reset_all!)
-
- post :reset_storage_health
+ expect(assigns[:errors]).not_to be_nil
end
end
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 2b28cfd16cc..be3fc832008 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -190,30 +190,6 @@ describe ApplicationController do
end
end
- describe 'rescue from Gitlab::Git::Storage::Inaccessible' do
- controller(described_class) do
- def index
- raise Gitlab::Git::Storage::Inaccessible.new('broken', 100)
- end
- end
-
- it 'renders a 503 when storage is not available' do
- sign_in(create(:user))
-
- get :index
-
- expect(response.status).to eq(503)
- end
-
- it 'renders includes a Retry-After header' do
- sign_in(create(:user))
-
- get :index
-
- expect(response.headers['Retry-After']).to eq(100)
- end
- end
-
describe 'response format' do
controller(described_class) do
def index
@@ -709,22 +685,34 @@ describe ApplicationController do
end
context 'html' do
+ subject { get :index, text: "hi \255" }
+
it 'renders 412' do
- get :index, text: "hi \255"
+ if Gitlab.rails5?
+ expect { subject }.to raise_error(ActionController::BadRequest)
+ else
+ subject
- expect(response).to have_gitlab_http_status(412)
- expect(response).to render_template :precondition_failed
+ expect(response).to have_gitlab_http_status(412)
+ expect(response).to render_template :precondition_failed
+ end
end
end
context 'js' do
+ subject { get :index, text: "hi \255", format: :js }
+
it 'renders 412' do
- get :index, text: "hi \255", format: :js
+ if Gitlab.rails5?
+ expect { subject }.to raise_error(ActionController::BadRequest)
+ else
+ subject
- json_response = JSON.parse(response.body)
+ json_response = JSON.parse(response.body)
- expect(response).to have_gitlab_http_status(412)
- expect(json_response['error']).to eq('Invalid UTF-8')
+ expect(response).to have_gitlab_http_status(412)
+ expect(json_response['error']).to eq('Invalid UTF-8')
+ end
end
end
end
diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb
index d800ad7c187..ec73c89cb11 100644
--- a/spec/controllers/health_controller_spec.rb
+++ b/spec/controllers/health_controller_spec.rb
@@ -14,48 +14,6 @@ describe HealthController do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
end
- describe '#storage_check' do
- before do
- allow(Gitlab::RequestContext).to receive(:client_ip).and_return(whitelisted_ip)
- end
-
- subject { post :storage_check }
-
- it 'checks all the configured storages' do
- expect(Gitlab::Git::Storage::Checker).to receive(:check_all).and_call_original
-
- subject
- end
-
- it 'returns the check interval' do
- stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true')
- stub_application_setting(circuitbreaker_check_interval: 10)
-
- subject
-
- expect(json_response['check_interval']).to eq(10)
- end
-
- context 'with failing storages', :broken_storage do
- before do
- stub_storage_settings(
- broken: { path: 'tmp/tests/non-existent-repositories' }
- )
- end
-
- it 'includes the failure information' do
- subject
-
- expected_results = [
- { 'storage' => 'broken', 'success' => false },
- { 'storage' => 'default', 'success' => true }
- ]
-
- expect(json_response['results']).to eq(expected_results)
- end
- end
- end
-
describe '#readiness' do
shared_context 'endpoint responding with readiness data' do
let(:request_params) { {} }
diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb
index 97ac11fd171..9201332c5c8 100644
--- a/spec/controllers/projects/clusters_controller_spec.rb
+++ b/spec/controllers/projects/clusters_controller_spec.rb
@@ -6,15 +6,19 @@ describe Projects::ClustersController do
set(:project) { create(:project) }
- describe 'GET index' do
- describe 'functionality' do
- let(:user) { create(:user) }
+ let(:user) { create(:user) }
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+ describe 'GET index' do
+ def go(params = {})
+ get :index, params.reverse_merge(namespace_id: project.namespace.to_param, project_id: project)
+ end
+
+ describe 'functionality' do
context 'when project has one or more clusters' do
let(:project) { create(:project) }
let!(:enabled_cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
@@ -33,10 +37,11 @@ describe Projects::ClustersController do
before do
allow(Clusters::Cluster).to receive(:paginates_per).and_return(1)
create_list(:cluster, 2, :provided_by_gcp, :production_environment, projects: [project])
- get :index, namespace_id: project.namespace, project_id: project, page: last_page
end
it 'redirects to the page' do
+ go(page: last_page)
+
expect(response).to have_gitlab_http_status(:ok)
expect(assigns(:clusters).current_page).to eq(last_page)
end
@@ -68,21 +73,14 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
+ end
+ describe 'GET new' do
def go
- get :index, namespace_id: project.namespace.to_param, project_id: project
+ get :new, namespace_id: project.namespace, project_id: project
end
- end
- describe 'GET new' do
describe 'functionality for new cluster' do
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
-
context 'when omniauth has been configured' do
let(:key) { 'secret-key' }
let(:session_key_for_redirect_uri) do
@@ -139,13 +137,6 @@ describe Projects::ClustersController do
end
describe 'functionality for existing cluster' do
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
-
it 'has new object' do
go
@@ -163,10 +154,6 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
-
- def go
- get :new, namespace_id: project.namespace, project_id: project
- end
end
describe 'POST create for new cluster' do
@@ -183,14 +170,11 @@ describe Projects::ClustersController do
}
end
- describe 'functionality' do
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ def go
+ post :create_gcp, params.merge(namespace_id: project.namespace, project_id: project)
+ end
+ describe 'functionality' do
context 'when access token is valid' do
before do
stub_google_api_validate_token
@@ -257,10 +241,6 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
-
- def go
- post :create_gcp, params.merge(namespace_id: project.namespace, project_id: project)
- end
end
describe 'POST create for existing cluster' do
@@ -277,14 +257,11 @@ describe Projects::ClustersController do
}
end
- describe 'functionality' do
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ def go
+ post :create_user, params.merge(namespace_id: project.namespace, project_id: project)
+ end
+ describe 'functionality' do
context 'when creates a cluster' do
it 'creates a new cluster' do
expect(ClusterProvisionWorker).to receive(:perform_async)
@@ -339,23 +316,19 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
-
- def go
- post :create_user, params.merge(namespace_id: project.namespace, project_id: project)
- end
end
describe 'GET status' do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
- describe 'functionality' do
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ def go
+ get :status, namespace_id: project.namespace,
+ project_id: project,
+ id: cluster,
+ format: :json
+ end
+ describe 'functionality' do
it "responds with matching schema" do
go
@@ -380,26 +353,18 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
-
- def go
- get :status, namespace_id: project.namespace,
- project_id: project,
- id: cluster,
- format: :json
- end
end
describe 'GET show' do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
- describe 'functionality' do
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ def go
+ get :show, namespace_id: project.namespace,
+ project_id: project,
+ id: cluster
+ end
+ describe 'functionality' do
it "renders view" do
go
@@ -418,73 +383,85 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
-
- def go
- get :show, namespace_id: project.namespace,
- project_id: project,
- id: cluster
- end
end
describe 'PUT update' do
- context 'when cluster is provided by GCP' do
- let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
- let(:user) { create(:user) }
-
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
- context 'when changing parameters' do
- let(:params) do
- {
- cluster: {
- enabled: false,
- name: 'my-new-cluster-name',
- platform_kubernetes_attributes: {
- namespace: 'my-namespace'
- }
- }
+ let(:params) do
+ {
+ cluster: {
+ enabled: false,
+ name: 'my-new-cluster-name',
+ platform_kubernetes_attributes: {
+ namespace: 'my-namespace'
}
- end
+ }
+ }
+ end
- it "updates and redirects back to show page" do
- go
+ def go(format: :html)
+ put :update, params.merge(namespace_id: project.namespace,
+ project_id: project,
+ id: cluster,
+ format: format
+ )
+ end
- cluster.reload
- expect(response).to redirect_to(project_cluster_path(project, cluster))
- expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
- expect(cluster.enabled).to be_falsey
- end
+ context 'when cluster is provided by GCP' do
+ it "updates and redirects back to show page" do
+ go
- it "does not change cluster name" do
- go
+ cluster.reload
+ expect(response).to redirect_to(project_cluster_path(project, cluster))
+ expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
+ expect(cluster.enabled).to be_falsey
+ end
- expect(cluster.name).to eq('test-cluster')
- end
+ it "does not change cluster name" do
+ go
- context 'when cluster is being created' do
- let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
+ cluster.reload
+ expect(cluster.name).to eq('test-cluster')
+ end
- it "rejects changes" do
- go
+ context 'when cluster is being created' do
+ let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to render_template(:show)
- expect(cluster.enabled).to be_truthy
- end
+ it "rejects changes" do
+ go
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template(:show)
+ expect(cluster.enabled).to be_truthy
end
end
end
context 'when cluster is provided by user' do
let(:cluster) { create(:cluster, :provided_by_user, projects: [project]) }
- let(:user) { create(:user) }
- before do
- project.add_maintainer(user)
- sign_in(user)
+ let(:params) do
+ {
+ cluster: {
+ enabled: false,
+ name: 'my-new-cluster-name',
+ platform_kubernetes_attributes: {
+ namespace: 'my-namespace'
+ }
+ }
+ }
+ end
+
+ it "updates and redirects back to show page" do
+ go
+
+ cluster.reload
+ expect(response).to redirect_to(project_cluster_path(project, cluster))
+ expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
+ expect(cluster.enabled).to be_falsey
+ expect(cluster.name).to eq('my-new-cluster-name')
+ expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
end
context 'when format is json' do
@@ -503,7 +480,7 @@ describe Projects::ClustersController do
end
it "updates and redirects back to show page" do
- go_json
+ go(format: :json)
cluster.reload
expect(response).to have_http_status(:no_content)
@@ -526,49 +503,18 @@ describe Projects::ClustersController do
end
it "rejects changes" do
- go_json
+ go(format: :json)
expect(response).to have_http_status(:bad_request)
end
end
end
end
-
- context 'when format is html' do
- context 'when update enabled' do
- let(:params) do
- {
- cluster: {
- enabled: false,
- name: 'my-new-cluster-name',
- platform_kubernetes_attributes: {
- namespace: 'my-namespace'
- }
- }
- }
- end
-
- it "updates and redirects back to show page" do
- go
-
- cluster.reload
- expect(response).to redirect_to(project_cluster_path(project, cluster))
- expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
- expect(cluster.enabled).to be_falsey
- expect(cluster.name).to eq('my-new-cluster-name')
- expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
- end
- end
- end
end
describe 'security' do
set(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
- let(:params) do
- { cluster: { enabled: false } }
- end
-
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(project) }
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
@@ -578,34 +524,20 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
-
- def go
- put :update, params.merge(namespace_id: project.namespace,
- project_id: project,
- id: cluster)
- end
-
- def go_json
- put :update, params.merge(namespace_id: project.namespace,
- project_id: project,
- id: cluster,
- format: :json)
- end
end
describe 'DELETE destroy' do
- describe 'functionality' do
- let(:user) { create(:user) }
+ let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ def go
+ delete :destroy, namespace_id: project.namespace,
+ project_id: project,
+ id: cluster
+ end
+ describe 'functionality' do
context 'when cluster is provided by GCP' do
context 'when cluster is created' do
- let!(:cluster) { create(:cluster, :provided_by_gcp, :production_environment, projects: [project]) }
-
it "destroys and redirects back to clusters list" do
expect { go }
.to change { Clusters::Cluster.count }.by(-1)
@@ -658,11 +590,5 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
-
- def go
- delete :destroy, namespace_id: project.namespace,
- project_id: project,
- id: cluster
- end
end
end
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 5c8180baf8a..1484676eea3 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -352,6 +352,10 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(json_response['has_trace']).to be true
end
end
+
+ it 'exposes the stage the job belongs to' do
+ expect(json_response['stage']).to eq('test')
+ end
end
context 'when requesting JSON job is triggered' do
diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
index 397cc79bde4..1e1ea9a7144 100644
--- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb
@@ -150,7 +150,6 @@ describe Projects::MergeRequests::ConflictsController do
'new_path' => path,
'blob_icon' => 'file-text-o',
'blob_path' => a_string_ending_with(path),
- 'blob_ace_mode' => 'ruby',
'content' => content)
end
end
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index aaa3e8dc821..790051dd933 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -2,10 +2,11 @@ require 'spec_helper'
describe "Admin Health Check", :feature do
include StubENV
+ set(:admin) { create(:admin) }
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- sign_in(create(:admin))
+ sign_in(admin)
end
describe '#show' do
@@ -56,27 +57,4 @@ describe "Admin Health Check", :feature do
expect(page).to have_content('The server is on fire')
end
end
-
- context 'with repository storage failures', :broken_storage do
- before do
- visit admin_health_check_path
- end
-
- it 'shows storage failure information' do
- hostname = Gitlab::Environment.hostname
- maximum_failures = Gitlab::CurrentSettings.current_application_settings
- .circuitbreaker_failure_count_threshold
- number_of_failures = maximum_failures + 1
-
- expect(page).to have_content("broken: #{number_of_failures} failed storage access attempts:")
- expect(page).to have_content("#{hostname}: #{number_of_failures} of #{maximum_failures} failures.")
- end
-
- it 'allows resetting storage failures' do
- click_button 'Reset git storage health information'
-
- expect(page).to have_content('Git storage health information has been reset')
- expect(page).not_to have_content('failed storage access attempt')
- end
- end
end
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 08fd9f8af2a..2cdbdcffbc3 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -125,7 +125,7 @@ describe 'Edit group settings' do
def save_group
page.within('.gs-general') do
- click_button 'Save group'
+ click_button 'Save changes'
end
end
end
diff --git a/spec/features/groups/share_lock_spec.rb b/spec/features/groups/share_lock_spec.rb
index 5bbe77019ca..704d9f12888 100644
--- a/spec/features/groups/share_lock_spec.rb
+++ b/spec/features/groups/share_lock_spec.rb
@@ -60,14 +60,14 @@ describe 'Group share with group lock' do
def enable_group_lock
page.within('.gs-permissions') do
check 'group_share_with_group_lock'
- click_on 'Save group'
+ click_on 'Save changes'
end
end
def disable_group_lock
page.within('.gs-permissions') do
uncheck 'group_share_with_group_lock'
- click_on 'Save group'
+ click_on 'Save changes'
end
end
end
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index ac961e98a61..4e6f73ef58a 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -101,4 +101,25 @@ describe 'Group show page' do
expect(page).to have_emoji('smile')
end
end
+
+ context 'where group has projects' do
+ let(:user) { create(:user) }
+
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ end
+
+ it 'allows users to sorts projects by most stars', :js do
+ project1 = create(:project, namespace: group, star_count: 2)
+ project2 = create(:project, namespace: group, star_count: 3)
+ project3 = create(:project, namespace: group, star_count: 0)
+
+ visit group_path(group, sort: :stars_desc)
+
+ expect(find('.group-row:nth-child(1) .namespace-title > a')).to have_content(project2.title)
+ expect(find('.group-row:nth-child(2) .namespace-title > a')).to have_content(project1.title)
+ expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title)
+ end
+ end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index e62bd6f8187..63aa26cf5fd 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -140,10 +140,13 @@ describe 'Group' do
visit path
end
+ it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="group[name]"]' },
+ { form: '.js-general-permissions-form', input: 'input[name="group[two_factor_grace_period]"]' }]
+
it 'saves new settings' do
page.within('.gs-general') do
fill_in 'group_name', with: new_name
- click_button 'Save group'
+ click_button 'Save changes'
end
expect(page).to have_content 'successfully updated'
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index a5dc9017699..f15129759de 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -423,7 +423,7 @@ describe 'Merge request > User sees merge widget', :js do
end
it 'shows test reports summary which includes the new failure' do
- within(".mr-section-container") do
+ within(".js-reports-container") do
click_button 'Expand'
expect(page).to have_content('Test summary contained 1 failed test result out of 2 total tests')
@@ -438,7 +438,7 @@ describe 'Merge request > User sees merge widget', :js do
context 'when user clicks the new failure' do
it 'shows the test report detail' do
- within(".mr-section-container") do
+ within(".js-reports-container") do
click_button 'Expand'
within(".js-report-section-container") do
@@ -468,7 +468,7 @@ describe 'Merge request > User sees merge widget', :js do
end
it 'shows test reports summary which includes the existing failure' do
- within(".mr-section-container") do
+ within(".js-reports-container") do
click_button 'Expand'
expect(page).to have_content('Test summary contained 1 failed test result out of 2 total tests')
@@ -483,7 +483,7 @@ describe 'Merge request > User sees merge widget', :js do
context 'when user clicks the existing failure' do
it 'shows test report detail of it' do
- within(".mr-section-container") do
+ within(".js-reports-container") do
click_button 'Expand'
within(".js-report-section-container") do
@@ -519,7 +519,7 @@ describe 'Merge request > User sees merge widget', :js do
end
it 'shows test reports summary which includes the resolved failure' do
- within(".mr-section-container") do
+ within(".js-reports-container") do
click_button 'Expand'
expect(page).to have_content('Test summary contained 1 fixed test result out of 2 total tests')
@@ -533,7 +533,7 @@ describe 'Merge request > User sees merge widget', :js do
context 'when user clicks the resolved failure' do
it 'shows test report detail of it' do
- within(".mr-section-container") do
+ within(".js-reports-container") do
click_button 'Expand'
within(".js-report-section-container") do
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 2076ce7b4f7..a3a301504ff 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -423,6 +423,31 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
+ context 'when job stops environment', :js do
+ let(:environment) { create(:environment, name: 'production', project: project) }
+ let(:build) do
+ create(
+ :ci_build,
+ :success,
+ :trace_live,
+ environment: environment.name,
+ pipeline: pipeline,
+ options: { environment: { action: 'stop' } }
+ )
+ end
+
+ before do
+ visit project_job_path(project, build)
+ wait_for_requests
+ end
+
+ it 'does not show environment information banner' do
+ expect(page).not_to have_selector('.js-environment-container')
+ expect(page).not_to have_selector('.environment-information')
+ expect(page).not_to have_text(environment.name)
+ end
+ end
+
describe 'environment info in job view', :js do
before do
visit project_job_path(project, job)
@@ -663,6 +688,56 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
expect(page).to have_content('This job does not have a trace.')
end
end
+
+ context 'with erased job', :js do
+ let(:job) { create(:ci_build, :erased, pipeline: pipeline) }
+
+ it 'renders erased job warning' do
+ visit project_job_path(project, job)
+ wait_for_requests
+
+ page.within('.js-job-erased-block') do
+ expect(page).to have_content('Job has been erased')
+ end
+ end
+ end
+
+ context 'without erased job', :js do
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'does not render erased job warning' do
+ visit project_job_path(project, job)
+ wait_for_requests
+
+ expect(page).not_to have_css('.js-job-erased-block')
+ end
+ end
+
+ context 'on mobile', :js do
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'renders collpased sidebar' do
+ page.current_window.resize_to(600, 800)
+
+ visit project_job_path(project, job)
+ wait_for_requests
+
+ expect(page).to have_css('.js-build-sidebar.right-sidebar-collapsed', visible: false)
+ expect(page).not_to have_css('.js-build-sidebar.right-sidebar-expanded', visible: false)
+ end
+ end
+
+ context 'on desktop', :js do
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'renders expanded sidebar' do
+ visit project_job_path(project, job)
+ wait_for_requests
+
+ expect(page).to have_css('.js-build-sidebar.right-sidebar-expanded')
+ expect(page).not_to have_css('.js-build-sidebar.right-sidebar-collpased')
+ end
+ end
end
describe "POST /:project/jobs/:id/cancel", :js do
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
index 6fe21579e8e..df2b492ae6b 100644
--- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -350,41 +350,6 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
end
end
end
-
- describe '"Set up Koding" button' do
- it 'no "Set up Koding" button if Koding disabled' do
- stub_application_setting(koding_enabled?: false)
-
- visit project_path(project)
-
- page.within('.project-stats') do
- expect(page).not_to have_link('Set up Koding')
- end
- end
-
- it 'no "Set up Koding" button if the project already has a .koding.yml' do
- stub_application_setting(koding_enabled?: true)
- allow(Gitlab::CurrentSettings.current_application_settings).to receive(:koding_url).and_return('http://koding.example.com')
- expect(project.repository.changelog).not_to be_nil
- allow_any_instance_of(Repository).to receive(:koding_yml).and_return(project.repository.changelog)
-
- visit project_path(project)
-
- page.within('.project-stats') do
- expect(page).not_to have_link('Set up Koding')
- end
- end
-
- it '"Set up Koding" button linked to new file populated for a .koding.yml' do
- stub_application_setting(koding_enabled?: true)
-
- visit project_path(project)
-
- page.within('.project-stats') do
- expect(page).to have_link('Set up Koding', href: presenter.add_koding_stack_path)
- end
- end
- end
end
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 8e310f38a8c..fb766addb31 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -193,6 +193,23 @@ describe 'Project' do
end
end
+ describe 'when the project repository is disabled', :js do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository_disabled, :repository, namespace: user.namespace) }
+
+ before do
+ sign_in(user)
+ project.add_maintainer(user)
+ visit project_path(project)
+ end
+
+ it 'does not show an error' do
+ wait_for_requests
+
+ expect(page).not_to have_selector('.flash-alert')
+ end
+ end
+
describe 'removal', :js do
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
index 149bd32e736..0c893e65d9c 100644
--- a/spec/features/security/dashboard_access_spec.rb
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -43,20 +43,6 @@ describe "Dashboard access" do
it { is_expected.to be_allowed_for :visitor }
end
- describe "GET /koding" do
- subject { koding_path }
-
- context 'with Koding enabled' do
- before do
- stub_application_setting(koding_enabled?: true)
- end
-
- it { is_expected.to be_allowed_for :admin }
- it { is_expected.to be_allowed_for :user }
- it { is_expected.to be_denied_for :visitor }
- end
- end
-
describe "GET /projects/new" do
it { expect(new_project_path).to be_allowed_for :admin }
it { expect(new_project_path).to be_allowed_for :user }
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
index a07edc42eae..72b53bae46a 100644
--- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -15,7 +15,7 @@ describe 'User uploads avatar to group' do
)
page.within('.gs-general') do
- click_button 'Save group'
+ click_button 'Save changes'
end
visit group_path(group)
diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb
index 11f357cbaa5..b0ff53f9ccb 100644
--- a/spec/features/users/overview_spec.rb
+++ b/spec/features/users/overview_spec.rb
@@ -104,8 +104,9 @@ describe 'Overview tab on a user profile', :js do
end
describe 'user has a personal project' do
- let(:private_project) { create(:project, :private, namespace: user.namespace, creator: user) { |p| p.add_maintainer(user) } }
- let!(:private_event) { create(:event, project: private_project, author: user) }
+ before do
+ create(:project, :private, namespace: user.namespace, creator: user) { |p| p.add_maintainer(user) }
+ end
include_context 'visit overview tab'
@@ -119,5 +120,31 @@ describe 'Overview tab on a user profile', :js do
expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: true)
end
end
+
+ describe 'user has more than ten personal projects' do
+ before do
+ create_list(:project, 11, :private, namespace: user.namespace, creator: user) do |project|
+ project.add_maintainer(user)
+ end
+ end
+
+ include_context 'visit overview tab'
+
+ it 'it shows max. ten entries in the list of projects' do
+ page.within('.projects-block') do
+ expect(page).to have_selector('.project-row', count: 10)
+ end
+ end
+
+ it 'shows a link to the project list' do
+ expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: true)
+ end
+
+ it 'does not show pagination' do
+ page.within('.projects-block') do
+ expect(page).not_to have_selector('.gl-pagination')
+ end
+ end
+ end
end
end
diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb
index 9e3f2c69606..7d164539d9a 100644
--- a/spec/finders/branches_finder_spec.rb
+++ b/spec/finders/branches_finder_spec.rb
@@ -66,7 +66,7 @@ describe BranchesFinder do
context 'filter and sort' do
it 'filters branches by name and sorts by recently_updated' do
- params = { sort: 'updated_desc', search: 'feature' }
+ params = { sort: 'updated_desc', search: 'feat' }
branches_finder = described_class.new(repository, params)
result = branches_finder.execute
@@ -75,6 +75,17 @@ describe BranchesFinder do
expect(result.count).to eq(2)
end
+ it 'filters branches by name and sorts by recently_updated, with exact matches first' do
+ params = { sort: 'updated_desc', search: 'feature' }
+ branches_finder = described_class.new(repository, params)
+
+ result = branches_finder.execute
+
+ expect(result.first.name).to eq('feature')
+ expect(result.second.name).to eq('feature_conflict')
+ expect(result.count).to eq(2)
+ end
+
it 'filters branches by name and sorts by last_updated' do
params = { sort: 'updated_asc', search: 'feature' }
branches_finder = described_class.new(repository, params)
@@ -84,6 +95,26 @@ describe BranchesFinder do
expect(result.first.name).to eq('feature')
expect(result.count).to eq(2)
end
+
+ it 'filters branches by name that begins with' do
+ params = { search: '^feature_' }
+ branches_finder = described_class.new(repository, params)
+
+ result = branches_finder.execute
+
+ expect(result.first.name).to eq('feature_conflict')
+ expect(result.count).to eq(1)
+ end
+
+ it 'filters branches by name that ends with' do
+ params = { search: 'feature$' }
+ branches_finder = described_class.new(repository, params)
+
+ result = branches_finder.execute
+
+ expect(result.first.name).to eq('feature')
+ expect(result.count).to eq(1)
+ end
end
end
end
diff --git a/spec/fixtures/api/schemas/deployment.json b/spec/fixtures/api/schemas/deployment.json
index 8c8cdf8bcb2..44835386cfc 100644
--- a/spec/fixtures/api/schemas/deployment.json
+++ b/spec/fixtures/api/schemas/deployment.json
@@ -20,12 +20,35 @@
"name"
],
"properties": {
- "name": { "type": "string" }
+ "name": { "type": "string" },
+ "ref_path": { "type": "string" }
},
"additionalProperties": false
},
"sha": { "type": "string" },
- "tag": { "type": "boolean" }
+ "tag": { "type": "boolean" },
+ "user": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "entities/user.json" }
+ ]
+ },
+ "commit": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "entities/commit.json" }
+ ]
+ },
+ "deployable": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "job/job.json" }
+ ]
+ },
+ "manual_actions": {
+ "type": "array",
+ "items": { "$ref": "job/job.json" }
+ }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/api/schemas/entities/commit.json b/spec/fixtures/api/schemas/entities/commit.json
index 686d29c97d2..324702e3f94 100644
--- a/spec/fixtures/api/schemas/entities/commit.json
+++ b/spec/fixtures/api/schemas/entities/commit.json
@@ -17,11 +17,10 @@
"author": {
"oneOf": [
{ "type": "null" },
- { "type": "user.json" }
+ { "$ref": "user.json" }
]
}
- },
- "additionalProperties": false
+ }
}
]
}
diff --git a/spec/fixtures/api/schemas/job/job_details.json b/spec/fixtures/api/schemas/job/job_details.json
index 07e674216fa..8218474705c 100644
--- a/spec/fixtures/api/schemas/job/job_details.json
+++ b/spec/fixtures/api/schemas/job/job_details.json
@@ -7,7 +7,8 @@
"artifact",
"runner",
"runners",
- "has_trace"
+ "has_trace",
+ "stage"
],
"properties": {
"artifact": { "$ref": "artifact.json" },
@@ -16,6 +17,7 @@
"deployment_status": { "$ref": "deployment_status.json" },
"runner": { "$ref": "runner.json" },
"runners": { "$ref": "runners.json" },
- "has_trace": { "type": "boolean" }
+ "has_trace": { "type": "boolean" },
+ "stage": { "type": "string" }
}
}
diff --git a/spec/fixtures/valid.po b/spec/fixtures/valid.po
index e43fd5fea15..dbe2f952bad 100644
--- a/spec/fixtures/valid.po
+++ b/spec/fixtures/valid.po
@@ -790,9 +790,6 @@ msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a travÃ
msgid "Set up CI"
msgstr "Configurar CI"
-msgid "Set up Koding"
-msgstr "Configurar Koding"
-
msgid "Set up auto deploy"
msgstr "Configurar auto despliegue"
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 363ebc88afd..c112c8ed633 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -2,6 +2,13 @@ require 'spec_helper'
describe PreferencesHelper do
describe '#dashboard_choices' do
+ let(:user) { build(:user) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ allow(helper).to receive(:can?).and_return(false)
+ end
+
it 'raises an exception when defined choices may be missing' do
expect(User).to receive(:dashboards).and_return(foo: 'foo')
expect { helper.dashboard_choices }.to raise_error(RuntimeError)
diff --git a/spec/helpers/storage_health_helper_spec.rb b/spec/helpers/storage_health_helper_spec.rb
deleted file mode 100644
index 874498e6338..00000000000
--- a/spec/helpers/storage_health_helper_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe StorageHealthHelper do
- describe '#failing_storage_health_message' do
- let(:health) do
- Gitlab::Git::Storage::Health.new(
- "<script>alert('storage name');)</script>",
- []
- )
- end
-
- it 'escapes storage names' do
- escaped_storage_name = '&lt;script&gt;alert(&#39;storage name&#39;);)&lt;/script&gt;'
-
- result = helper.failing_storage_health_message(health)
-
- expect(result).to include(escaped_storage_name)
- end
- end
-end
diff --git a/spec/javascripts/.eslintrc.yml b/spec/javascripts/.eslintrc.yml
index 9b2c84ce9f5..1328b7c362b 100644
--- a/spec/javascripts/.eslintrc.yml
+++ b/spec/javascripts/.eslintrc.yml
@@ -37,7 +37,4 @@ rules:
- 'fixtures/blob'
# Temporarily disabled to facilitate an upgrade to eslint-plugin-jasmine
jasmine/new-line-before-expect: off
- jasmine/new-line-between-declarations: off
- jasmine/no-promise-without-done-fail: off
- jasmine/prefer-jasmine-matcher: off
jasmine/prefer-toHaveBeenCalledWith: off
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 0c5d68990d5..3f84a46e8d9 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -55,6 +55,7 @@ import '~/lib/utils/common_utils';
});
};
});
+
afterEach(function() {
// restore original url root value
gon.relative_url_root = urlRoot;
@@ -64,6 +65,7 @@ import '~/lib/utils/common_utils';
awardsHandler.destroy();
});
+
describe('::showEmojiMenu', function() {
it('should show emoji menu when Add emoji button clicked', function(done) {
$('.js-add-award')
@@ -78,6 +80,7 @@ import '~/lib/utils/common_utils';
return expect($('.js-awards-block.current').length).toBe(1);
});
});
+
it('should also show emoji menu for the smiley icon in notes', function(done) {
$('.js-add-award.note-action-button').click();
return lazyAssert(done, function() {
@@ -85,6 +88,7 @@ import '~/lib/utils/common_utils';
return expect($emojiMenu.length).toBe(1);
});
});
+
it('should remove emoji menu when body is clicked', function(done) {
$('.js-add-award')
.eq(0)
@@ -98,6 +102,7 @@ import '~/lib/utils/common_utils';
return expect($('.js-awards-block.current').length).toBe(0);
});
});
+
it('should not remove emoji menu when search is clicked', function(done) {
$('.js-add-award')
.eq(0)
@@ -123,6 +128,7 @@ import '~/lib/utils/common_utils';
expect($emojiButton.next('.js-counter').text()).toBe('1');
return expect($votesBlock.hasClass('hidden')).toBe(false);
});
+
it('should remove the emoji when we click again', function() {
var $emojiButton, $votesBlock;
$votesBlock = $('.js-awards-block').eq(0);
@@ -142,6 +148,7 @@ import '~/lib/utils/common_utils';
return expect($emojiButton.next('.js-counter').text()).toBe('4');
});
});
+
describe('::userAuthored', function() {
it('should update tooltip to user authored title', function() {
var $thumbsUpEmoji, $votesBlock;
@@ -153,6 +160,7 @@ import '~/lib/utils/common_utils';
'You cannot vote on your own issue, MR and note',
);
});
+
it('should restore tooltip back to initial vote list', function() {
var $thumbsUpEmoji, $votesBlock;
jasmine.clock().install();
@@ -165,6 +173,7 @@ import '~/lib/utils/common_utils';
return expect($thumbsUpEmoji.data('originalTitle')).toBe('sam');
});
});
+
describe('::getAwardUrl', function() {
return it('returns the url for request', function() {
return expect(awardsHandler.getAwardUrl()).toBe(
@@ -172,6 +181,7 @@ import '~/lib/utils/common_utils';
);
});
});
+
describe('::addAward and ::checkMutuality', function() {
return it('should handle :+1: and :-1: mutuality', function() {
var $thumbsDownEmoji, $thumbsUpEmoji, $votesBlock, awardUrl;
@@ -189,6 +199,7 @@ import '~/lib/utils/common_utils';
return expect($thumbsDownEmoji.hasClass('active')).toBe(true);
});
});
+
describe('::removeEmoji', function() {
return it('should remove emoji', function() {
var $votesBlock, awardUrl;
@@ -200,6 +211,7 @@ import '~/lib/utils/common_utils';
return expect($votesBlock.find('[data-name=fire]').length).toBe(0);
});
});
+
describe('::addYouToUserList', function() {
it('should prepend "You" to the award tooltip', function() {
var $thumbsUpEmoji, $votesBlock, awardUrl;
@@ -222,6 +234,7 @@ import '~/lib/utils/common_utils';
return expect($thumbsUpEmoji.data('originalTitle')).toBe('You and sam');
});
});
+
describe('::removeYouToUserList', function() {
it('removes "You" from the front of the tooltip', function() {
var $thumbsUpEmoji, $votesBlock, awardUrl;
@@ -246,6 +259,7 @@ import '~/lib/utils/common_utils';
return expect($thumbsUpEmoji.data('originalTitle')).toBe('sam');
});
});
+
describe('::searchEmojis', () => {
it('should filter the emoji', function(done) {
return openAndWaitForEmojiMenu()
@@ -263,6 +277,7 @@ import '~/lib/utils/common_utils';
done.fail(`Failed to open and build emoji menu: ${err.message}`);
});
});
+
it('should clear the search when searching for nothing', function(done) {
return openAndWaitForEmojiMenu()
.then(() => {
@@ -305,6 +320,7 @@ import '~/lib/utils/common_utils';
done.fail(`Failed to open and build emoji menu: ${err.message}`);
});
});
+
it('should remove already selected emoji', function(done) {
return openEmojiMenuAndAddEmoji()
.then(() => {
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index d8aa5c636da..b3a5eac8982 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -58,12 +58,14 @@ describe('Quick Submit behavior', function () {
expect(submitButton).toBeDisabled();
});
+
it('disables button of type submit', () => {
const submitButton = $('.js-quick-submit input[type=submit]');
this.textarea.trigger(keydownEvent());
expect(submitButton).toBeDisabled();
});
+
it('only clicks one submit', () => {
const existingSubmit = $('.js-quick-submit input[type=submit]');
// Add an extra submit button
diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js
index 0e4e1697fd0..50505b41313 100644
--- a/spec/javascripts/boards/board_blank_state_spec.js
+++ b/spec/javascripts/boards/board_blank_state_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import '~/boards/stores/boards_store';
+import boardsStore from '~/boards/stores/boards_store';
import BoardBlankState from '~/boards/components/board_blank_state.vue';
import { mockBoardService } from './mock_data';
@@ -10,7 +10,7 @@ describe('Boards blank state', () => {
beforeEach((done) => {
const Comp = Vue.extend(BoardBlankState);
- gl.issueBoards.BoardsStore.create();
+ boardsStore.create();
gl.boardService = mockBoardService();
spyOn(gl.boardService, 'generateDefaultLists').and.callFake(() => new Promise((resolve, reject) => {
@@ -57,7 +57,7 @@ describe('Boards blank state', () => {
vm.$el.querySelector('.btn-default').click();
setTimeout(() => {
- expect(gl.issueBoards.BoardsStore.welcomeIsHidden()).toBeTruthy();
+ expect(boardsStore.welcomeIsHidden()).toBeTruthy();
done();
});
@@ -67,9 +67,9 @@ describe('Boards blank state', () => {
vm.$el.querySelector('.btn-success').click();
setTimeout(() => {
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
- expect(gl.issueBoards.BoardsStore.state.lists[0].title).toEqual('To Do');
- expect(gl.issueBoards.BoardsStore.state.lists[1].title).toEqual('Doing');
+ expect(boardsStore.state.lists.length).toBe(2);
+ expect(boardsStore.state.lists[0].title).toEqual('To Do');
+ expect(boardsStore.state.lists[1].title).toEqual('Doing');
done();
});
@@ -81,8 +81,8 @@ describe('Boards blank state', () => {
vm.$el.querySelector('.btn-success').click();
setTimeout(() => {
- expect(gl.issueBoards.BoardsStore.welcomeIsHidden()).toBeFalsy();
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
+ expect(boardsStore.welcomeIsHidden()).toBeFalsy();
+ expect(boardsStore.state.lists.length).toBe(1);
done();
});
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index ad263791cd4..20cfe426807 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -10,7 +10,7 @@ import eventHub from '~/boards/eventhub';
import '~/vue_shared/models/label';
import '~/vue_shared/models/assignee';
import '~/boards/models/list';
-import '~/boards/stores/boards_store';
+import boardsStore from '~/boards/stores/boards_store';
import boardCard from '~/boards/components/board_card.vue';
import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
@@ -23,8 +23,8 @@ describe('Board card', () => {
mock.onAny().reply(boardsMockInterceptor);
gl.boardService = mockBoardService();
- gl.issueBoards.BoardsStore.create();
- gl.issueBoards.BoardsStore.detail.issue = {};
+ boardsStore.create();
+ boardsStore.detail.issue = {};
const BoardCardComp = Vue.extend(boardCard);
const list = new List(listObj);
@@ -62,7 +62,7 @@ describe('Board card', () => {
});
it('returns true when detailIssue is equal to card issue', () => {
- gl.issueBoards.BoardsStore.detail.issue = vm.issue;
+ boardsStore.detail.issue = vm.issue;
expect(vm.issueDetailVisible).toBe(true);
});
@@ -119,19 +119,19 @@ describe('Board card', () => {
});
it('does not set detail issue if showDetail is false', () => {
- expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ expect(boardsStore.detail.issue).toEqual({});
});
it('does not set detail issue if link is clicked', () => {
triggerEvent('mouseup', vm.$el.querySelector('a'));
- expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ expect(boardsStore.detail.issue).toEqual({});
});
it('does not set detail issue if button is clicked', () => {
triggerEvent('mouseup', vm.$el.querySelector('button'));
- expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ expect(boardsStore.detail.issue).toEqual({});
});
it('does not set detail issue if img is clicked', (done) => {
@@ -145,7 +145,7 @@ describe('Board card', () => {
Vue.nextTick(() => {
triggerEvent('mouseup', vm.$el.querySelector('img'));
- expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ expect(boardsStore.detail.issue).toEqual({});
done();
});
@@ -154,7 +154,7 @@ describe('Board card', () => {
it('does not set detail issue if showDetail is false after mouseup', () => {
triggerEvent('mouseup');
- expect(gl.issueBoards.BoardsStore.detail.issue).toEqual({});
+ expect(boardsStore.detail.issue).toEqual({});
});
it('sets detail issue to card issue on mouse up', () => {
@@ -164,7 +164,7 @@ describe('Board card', () => {
triggerEvent('mouseup');
expect(eventHub.$emit).toHaveBeenCalledWith('newDetailIssue', vm.issue);
- expect(gl.issueBoards.BoardsStore.detail.list).toEqual(vm.list);
+ expect(boardsStore.detail.list).toEqual(vm.list);
});
it('adds active class if detail issue is set', (done) => {
@@ -181,7 +181,7 @@ describe('Board card', () => {
it('resets detail issue to empty if already set', () => {
spyOn(eventHub, '$emit');
- gl.issueBoards.BoardsStore.detail.issue = vm.issue;
+ boardsStore.detail.issue = vm.issue;
triggerEvent('mousedown');
triggerEvent('mouseup');
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 290600cf995..037e06cf3b2 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -1,15 +1,15 @@
/* global List */
/* global ListIssue */
+
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Sortable from 'sortablejs';
import BoardList from '~/boards/components/board_list.vue';
import eventHub from '~/boards/eventhub';
-import '~/boards/mixins/sortable_default_options';
import '~/boards/models/issue';
import '~/boards/models/list';
-import '~/boards/stores/boards_store';
+import boardsStore from '~/boards/stores/boards_store';
import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
window.Sortable = Sortable;
@@ -25,8 +25,7 @@ describe('Board list component', () => {
mock = new MockAdapter(axios);
mock.onAny().reply(boardsMockInterceptor);
gl.boardService = mockBoardService();
- gl.issueBoards.BoardsStore.create();
- gl.IssueBoardsApp = new Vue();
+ boardsStore.create();
const BoardListComp = Vue.extend(BoardList);
const list = new List(listObj);
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index 1245e3e099a..9fea625a4ac 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -4,6 +4,7 @@ import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import boardNewIssue from '~/boards/components/board_new_issue.vue';
+import boardsStore from '~/boards/stores/boards_store';
import '~/boards/models/list';
import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
@@ -36,8 +37,7 @@ describe('Issue boards new issue form', () => {
mock.onAny().reply(boardsMockInterceptor);
gl.boardService = mockBoardService();
- gl.issueBoards.BoardsStore.create();
- gl.IssueBoardsApp = new Vue();
+ boardsStore.create();
list = new List(listObj);
@@ -148,13 +148,13 @@ describe('Issue boards new issue form', () => {
});
it('sets detail issue after submit', (done) => {
- expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe(undefined);
+ expect(boardsStore.detail.issue.title).toBe(undefined);
vm.title = 'submit issue';
Vue.nextTick()
.then(submitIssue)
.then(() => {
- expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue');
+ expect(boardsStore.detail.issue.title).toBe('submit issue');
})
.then(done)
.catch(done.fail);
@@ -166,7 +166,7 @@ describe('Issue boards new issue form', () => {
Vue.nextTick()
.then(submitIssue)
.then(() => {
- expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id);
+ expect(boardsStore.detail.list.id).toBe(list.id);
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index ed43ce9029e..dfd3ea0db66 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -11,7 +11,7 @@ import '~/vue_shared/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
-import '~/boards/stores/boards_store';
+import boardsStore from '~/boards/stores/boards_store';
import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data';
describe('Store', () => {
@@ -21,7 +21,7 @@ describe('Store', () => {
mock = new MockAdapter(axios);
mock.onAny().reply(boardsMockInterceptor);
gl.boardService = mockBoardService();
- gl.issueBoards.BoardsStore.create();
+ boardsStore.create();
spyOn(gl.boardService, 'moveIssue').and.callFake(() => new Promise((resolve) => {
resolve();
@@ -38,35 +38,35 @@ describe('Store', () => {
});
it('starts with a blank state', () => {
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0);
+ expect(boardsStore.state.lists.length).toBe(0);
});
describe('lists', () => {
it('creates new list without persisting to DB', () => {
- gl.issueBoards.BoardsStore.addList(listObj);
+ boardsStore.addList(listObj);
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
+ expect(boardsStore.state.lists.length).toBe(1);
});
it('finds list by ID', () => {
- gl.issueBoards.BoardsStore.addList(listObj);
- const list = gl.issueBoards.BoardsStore.findList('id', listObj.id);
+ boardsStore.addList(listObj);
+ const list = boardsStore.findList('id', listObj.id);
expect(list.id).toBe(listObj.id);
});
it('finds list by type', () => {
- gl.issueBoards.BoardsStore.addList(listObj);
- const list = gl.issueBoards.BoardsStore.findList('type', 'label');
+ boardsStore.addList(listObj);
+ const list = boardsStore.findList('type', 'label');
expect(list).toBeDefined();
});
it('gets issue when new list added', (done) => {
- gl.issueBoards.BoardsStore.addList(listObj);
- const list = gl.issueBoards.BoardsStore.findList('id', listObj.id);
+ boardsStore.addList(listObj);
+ const list = boardsStore.findList('id', listObj.id);
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
+ expect(boardsStore.state.lists.length).toBe(1);
setTimeout(() => {
expect(list.issues.length).toBe(1);
@@ -76,7 +76,7 @@ describe('Store', () => {
});
it('persists new list', (done) => {
- gl.issueBoards.BoardsStore.new({
+ boardsStore.new({
title: 'Test',
list_type: 'label',
label: {
@@ -86,10 +86,10 @@ describe('Store', () => {
description: 'testing;'
}
});
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
+ expect(boardsStore.state.lists.length).toBe(1);
setTimeout(() => {
- const list = gl.issueBoards.BoardsStore.findList('id', listObj.id);
+ const list = boardsStore.findList('id', listObj.id);
expect(list).toBeDefined();
expect(list.id).toBe(listObj.id);
expect(list.position).toBe(0);
@@ -98,61 +98,61 @@ describe('Store', () => {
});
it('check for blank state adding', () => {
- expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true);
+ expect(boardsStore.shouldAddBlankState()).toBe(true);
});
it('check for blank state not adding', () => {
- gl.issueBoards.BoardsStore.addList(listObj);
- expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(false);
+ boardsStore.addList(listObj);
+ expect(boardsStore.shouldAddBlankState()).toBe(false);
});
it('check for blank state adding when closed list exist', () => {
- gl.issueBoards.BoardsStore.addList({
+ boardsStore.addList({
list_type: 'closed'
});
- expect(gl.issueBoards.BoardsStore.shouldAddBlankState()).toBe(true);
+ expect(boardsStore.shouldAddBlankState()).toBe(true);
});
it('adds the blank state', () => {
- gl.issueBoards.BoardsStore.addBlankState();
+ boardsStore.addBlankState();
- const list = gl.issueBoards.BoardsStore.findList('type', 'blank', 'blank');
+ const list = boardsStore.findList('type', 'blank', 'blank');
expect(list).toBeDefined();
});
it('removes list from state', () => {
- gl.issueBoards.BoardsStore.addList(listObj);
+ boardsStore.addList(listObj);
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
+ expect(boardsStore.state.lists.length).toBe(1);
- gl.issueBoards.BoardsStore.removeList(listObj.id, 'label');
+ boardsStore.removeList(listObj.id, 'label');
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0);
+ expect(boardsStore.state.lists.length).toBe(0);
});
it('moves the position of lists', () => {
- const listOne = gl.issueBoards.BoardsStore.addList(listObj);
- const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+ const listOne = boardsStore.addList(listObj);
+ const listTwo = boardsStore.addList(listObjDuplicate);
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
+ expect(boardsStore.state.lists.length).toBe(2);
- gl.issueBoards.BoardsStore.moveList(listOne, [listObjDuplicate.id, listObj.id]);
+ boardsStore.moveList(listOne, [listObjDuplicate.id, listObj.id]);
expect(listOne.position).toBe(1);
});
it('moves an issue from one list to another', (done) => {
- const listOne = gl.issueBoards.BoardsStore.addList(listObj);
- const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+ const listOne = boardsStore.addList(listObj);
+ const listTwo = boardsStore.addList(listObjDuplicate);
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
+ expect(boardsStore.state.lists.length).toBe(2);
setTimeout(() => {
expect(listOne.issues.length).toBe(1);
expect(listTwo.issues.length).toBe(1);
- gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(1));
+ boardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(1));
expect(listOne.issues.length).toBe(0);
expect(listTwo.issues.length).toBe(1);
@@ -162,19 +162,19 @@ describe('Store', () => {
});
it('moves an issue from backlog to a list', (done) => {
- const backlog = gl.issueBoards.BoardsStore.addList({
+ const backlog = boardsStore.addList({
...listObj,
list_type: 'backlog',
});
- const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+ const listTwo = boardsStore.addList(listObjDuplicate);
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
+ expect(boardsStore.state.lists.length).toBe(2);
setTimeout(() => {
expect(backlog.issues.length).toBe(1);
expect(listTwo.issues.length).toBe(1);
- gl.issueBoards.BoardsStore.moveIssueToList(backlog, listTwo, backlog.findIssue(1));
+ boardsStore.moveIssueToList(backlog, listTwo, backlog.findIssue(1));
expect(backlog.issues.length).toBe(0);
expect(listTwo.issues.length).toBe(1);
@@ -184,10 +184,10 @@ describe('Store', () => {
});
it('moves issue to top of another list', (done) => {
- const listOne = gl.issueBoards.BoardsStore.addList(listObj);
- const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+ const listOne = boardsStore.addList(listObj);
+ const listTwo = boardsStore.addList(listObjDuplicate);
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
+ expect(boardsStore.state.lists.length).toBe(2);
setTimeout(() => {
listOne.issues[0].id = 2;
@@ -195,7 +195,7 @@ describe('Store', () => {
expect(listOne.issues.length).toBe(1);
expect(listTwo.issues.length).toBe(1);
- gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(2), 0);
+ boardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(2), 0);
expect(listOne.issues.length).toBe(0);
expect(listTwo.issues.length).toBe(2);
@@ -207,10 +207,10 @@ describe('Store', () => {
});
it('moves issue to bottom of another list', (done) => {
- const listOne = gl.issueBoards.BoardsStore.addList(listObj);
- const listTwo = gl.issueBoards.BoardsStore.addList(listObjDuplicate);
+ const listOne = boardsStore.addList(listObj);
+ const listTwo = boardsStore.addList(listObjDuplicate);
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(2);
+ expect(boardsStore.state.lists.length).toBe(2);
setTimeout(() => {
listOne.issues[0].id = 2;
@@ -218,7 +218,7 @@ describe('Store', () => {
expect(listOne.issues.length).toBe(1);
expect(listTwo.issues.length).toBe(1);
- gl.issueBoards.BoardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(2), 1);
+ boardsStore.moveIssueToList(listOne, listTwo, listOne.findIssue(2), 1);
expect(listOne.issues.length).toBe(0);
expect(listTwo.issues.length).toBe(2);
@@ -238,14 +238,14 @@ describe('Store', () => {
labels: [],
assignees: [],
});
- const list = gl.issueBoards.BoardsStore.addList(listObj);
+ const list = boardsStore.addList(listObj);
setTimeout(() => {
list.addIssue(issue);
expect(list.issues.length).toBe(2);
- gl.issueBoards.BoardsStore.moveIssueInList(list, issue, 0, 1, [1, 2]);
+ boardsStore.moveIssueInList(list, issue, 0, 1, [1, 2]);
expect(list.issues[0].id).toBe(2);
expect(gl.boardService.moveIssue).toHaveBeenCalledWith(2, null, null, 1, null);
diff --git a/spec/javascripts/boards/components/board_spec.js b/spec/javascripts/boards/components/board_spec.js
index 19346e305cf..d4c53bd5a7d 100644
--- a/spec/javascripts/boards/components/board_spec.js
+++ b/spec/javascripts/boards/components/board_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import '~/boards/services/board_service';
-import '~/boards/components/board';
+import Board from '~/boards/components/board';
import '~/boards/models/list';
import { mockBoardService } from '../mock_data';
@@ -21,7 +21,7 @@ describe('Board component', () => {
boardId: 1,
});
- vm = new gl.issueBoards.Board({
+ vm = new Board({
propsData: {
boardId: '1',
disabled: false,
@@ -112,6 +112,6 @@ describe('Board component', () => {
).toBe(true);
done();
- });
+ }).catch(done.fail);
});
});
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index b6c61e7bad7..58b7d45d913 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -8,7 +8,6 @@ import '~/vue_shared/models/label';
import '~/vue_shared/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
-import '~/boards/stores/boards_store';
import IssueCardInner from '~/boards/components/issue_card_inner.vue';
import { listObj } from './mock_data';
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index 0beb5782283..e8387068831 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -6,7 +6,7 @@ import '~/vue_shared/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
-import '~/boards/stores/boards_store';
+import boardsStore from '~/boards/stores/boards_store';
import { mockBoardService } from './mock_data';
describe('Issue model', () => {
@@ -14,7 +14,7 @@ describe('Issue model', () => {
beforeEach(() => {
gl.boardService = mockBoardService();
- gl.issueBoards.BoardsStore.create();
+ boardsStore.create();
issue = new ListIssue({
title: 'Testing',
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index 4232e0fc221..ba6e81a29a9 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -9,7 +9,7 @@ import '~/vue_shared/models/assignee';
import '~/boards/models/issue';
import '~/boards/models/list';
import '~/boards/services/board_service';
-import '~/boards/stores/boards_store';
+import boardsStore from '~/boards/stores/boards_store';
import { listObj, listObjDuplicate, boardsMockInterceptor, mockBoardService } from './mock_data';
describe('List model', () => {
@@ -22,7 +22,7 @@ describe('List model', () => {
gl.boardService = mockBoardService({
bulkUpdatePath: '/test/issue-boards/board/1/lists',
});
- gl.issueBoards.BoardsStore.create();
+ boardsStore.create();
list = new List(listObj);
});
@@ -58,13 +58,13 @@ describe('List model', () => {
});
it('destroys the list', (done) => {
- gl.issueBoards.BoardsStore.addList(listObj);
- list = gl.issueBoards.BoardsStore.findList('id', listObj.id);
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(1);
+ boardsStore.addList(listObj);
+ list = boardsStore.findList('id', listObj.id);
+ expect(boardsStore.state.lists.length).toBe(1);
list.destroy();
setTimeout(() => {
- expect(gl.issueBoards.BoardsStore.state.lists.length).toBe(0);
+ expect(boardsStore.state.lists.length).toBe(0);
done();
}, 0);
});
diff --git a/spec/javascripts/boards/mock_data.js b/spec/javascripts/boards/mock_data.js
index f380ef450db..c28e41ec175 100644
--- a/spec/javascripts/boards/mock_data.js
+++ b/spec/javascripts/boards/mock_data.js
@@ -1,4 +1,5 @@
-/* global BoardService */
+import BoardService from '~/boards/services/board_service';
+
export const listObj = {
id: 300,
position: 0,
diff --git a/spec/javascripts/bootstrap_jquery_spec.js b/spec/javascripts/bootstrap_jquery_spec.js
index 052465d8d88..cd61d920fa0 100644
--- a/spec/javascripts/bootstrap_jquery_spec.js
+++ b/spec/javascripts/bootstrap_jquery_spec.js
@@ -9,6 +9,7 @@ import '~/commons/bootstrap';
beforeEach(function() {
return setFixtures('<input type="text" />');
});
+
it('adds the disabled attribute', function() {
var $input;
$input = $('input').first();
@@ -26,6 +27,7 @@ import '~/commons/bootstrap';
beforeEach(function() {
return setFixtures('<input type="text" disabled="disabled" class="disabled" />');
});
+
it('removes the disabled attribute', function() {
var $input;
$input = $('input').first();
diff --git a/spec/javascripts/datetime_utility_spec.js b/spec/javascripts/datetime_utility_spec.js
index 6c3e73f134e..24e4871ce44 100644
--- a/spec/javascripts/datetime_utility_spec.js
+++ b/spec/javascripts/datetime_utility_spec.js
@@ -158,9 +158,9 @@ describe('getTimeframeWindowFrom', () => {
const timeframe = datetimeUtility.getTimeframeWindowFrom(startDate, 5);
expect(timeframe.length).toBe(5);
timeframe.forEach((timeframeItem, index) => {
- expect(timeframeItem.getFullYear() === mockTimeframe[index].getFullYear()).toBe(true);
- expect(timeframeItem.getMonth() === mockTimeframe[index].getMonth()).toBe(true);
- expect(timeframeItem.getDate() === mockTimeframe[index].getDate()).toBeTruthy();
+ expect(timeframeItem.getFullYear()).toBe(mockTimeframe[index].getFullYear());
+ expect(timeframeItem.getMonth()).toBe(mockTimeframe[index].getMonth());
+ expect(timeframeItem.getDate()).toBe(mockTimeframe[index].getDate());
});
});
});
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index c986ea604b2..1f7d5f42322 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -6,6 +6,8 @@ import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+Vue.use(Vuex);
+
const discussionFixture = 'merge_requests/diff_discussion.json';
describe('diff_file_header', () => {
@@ -58,19 +60,19 @@ describe('diff_file_header', () => {
describe('titleLink', () => {
beforeEach(() => {
+ props.discussionPath = 'link://to/discussion';
Object.assign(props.diffFile, {
- fileHash: 'badc0ffee',
submoduleLink: 'link://to/submodule',
submoduleTreeUrl: 'some://tree/url',
});
});
- it('returns the fileHash for files', () => {
+ it('returns the discussionPath for files', () => {
props.diffFile.submodule = false;
vm = mountComponentWithStore(Component, { props, store });
- expect(vm.titleLink).toBe(`#${props.diffFile.fileHash}`);
+ expect(vm.titleLink).toBe(props.discussionPath);
});
it('returns the submoduleTreeUrl for submodules', () => {
@@ -91,6 +93,13 @@ describe('diff_file_header', () => {
expect(vm.titleLink).toBe(props.diffFile.submoduleLink);
});
+
+ it('sets the correct path to the discussion', () => {
+ props.discussionPath = 'link://to/discussion';
+ vm = mountComponentWithStore(Component, { props, store });
+ const href = vm.$el.querySelector('.js-title-wrapper').getAttribute('href');
+ expect(href).toBe(vm.discussionPath);
+ });
});
describe('filePath', () => {
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index 13859f43e98..b8d4b31ee04 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -24,14 +24,14 @@ describe('DiffFile', () => {
expect(el.querySelectorAll('.diff-content.hidden').length).toEqual(0);
expect(el.querySelector('.js-file-title')).toBeDefined();
- expect(el.querySelector('.file-title-name').innerText.indexOf(filePath) > -1).toEqual(true);
+ expect(el.querySelector('.file-title-name').innerText.indexOf(filePath)).toBeGreaterThan(-1);
expect(el.querySelector('.js-syntax-highlight')).toBeDefined();
expect(vm.file.renderIt).toEqual(false);
vm.file.renderIt = true;
vm.$nextTick(() => {
- expect(el.querySelectorAll('.line_content').length > 5).toEqual(true);
+ expect(el.querySelectorAll('.line_content').length).toBeGreaterThan(5);
});
});
@@ -98,9 +98,7 @@ describe('DiffFile', () => {
'This source diff could not be displayed because it is too large',
);
expect(vm.$el.querySelector('.js-too-large-diff')).toBeDefined();
- expect(vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK) > -1).toEqual(
- true,
- );
+ expect(vm.$el.querySelector('.js-too-large-diff a').href.indexOf(BLOB_LINK)).toBeGreaterThan(-1);
done();
});
diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
index 663c0680845..f36454cc23e 100644
--- a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
@@ -94,7 +94,7 @@ describe('DiffLineGutterContent', () => {
const component = createComponent({ lineNumber, lineCode });
const link = component.$el.querySelector('a');
- expect(link.href.indexOf(`#${lineCode}`) > -1).toEqual(true);
+ expect(link.href.indexOf(`#${lineCode}`)).toBeGreaterThan(-1);
expect(link.dataset.linenumber).toEqual(lineNumber.toString());
});
diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js
index b02328dd359..705558e860b 100644
--- a/spec/javascripts/diffs/components/inline_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js
@@ -27,7 +27,7 @@ describe('InlineDiffView', () => {
expect(el.querySelectorAll('tr.line_holder').length).toEqual(6);
expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(2);
expect(el.querySelectorAll('tr.line_holder.match').length).toEqual(1);
- expect(el.textContent.indexOf('Bad dates') > -1).toEqual(true);
+ expect(el.textContent.indexOf('Bad dates')).toBeGreaterThan(-1);
});
it('should render discussions', done => {
@@ -37,7 +37,7 @@ describe('InlineDiffView', () => {
Vue.nextTick(() => {
expect(el.querySelectorAll('.notes_holder').length).toEqual(1);
expect(el.querySelectorAll('.notes_holder .note-discussion li').length).toEqual(5);
- expect(el.innerText.indexOf('comment 5') > -1).toEqual(true);
+ expect(el.innerText.indexOf('comment 5')).toBeGreaterThan(-1);
component.$store.dispatch('setInitialNotes', []);
done();
diff --git a/spec/javascripts/dirty_submit/dirty_submit_collection_spec.js b/spec/javascripts/dirty_submit/dirty_submit_collection_spec.js
new file mode 100644
index 00000000000..87a26183b63
--- /dev/null
+++ b/spec/javascripts/dirty_submit/dirty_submit_collection_spec.js
@@ -0,0 +1,25 @@
+import DirtySubmitCollection from '~/dirty_submit/dirty_submit_collection';
+import { setInput, createForm } from './helper';
+
+describe('DirtySubmitCollection', () => {
+ it('disables submits until there are changes', done => {
+ const testElementsCollection = [createForm(), createForm()];
+ const forms = testElementsCollection.map(testElements => testElements.form);
+
+ new DirtySubmitCollection(forms); // eslint-disable-line no-new
+
+ testElementsCollection.forEach(testElements => {
+ const { input, submit } = testElements;
+ const originalValue = input.value;
+
+ expect(submit.disabled).toBe(true);
+
+ return setInput(input, `${originalValue} changes`)
+ .then(() => expect(submit.disabled).toBe(false))
+ .then(() => setInput(input, originalValue))
+ .then(() => expect(submit.disabled).toBe(true))
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/dirty_submit/dirty_submit_factory_spec.js b/spec/javascripts/dirty_submit/dirty_submit_factory_spec.js
new file mode 100644
index 00000000000..40843a68582
--- /dev/null
+++ b/spec/javascripts/dirty_submit/dirty_submit_factory_spec.js
@@ -0,0 +1,18 @@
+import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
+import DirtySubmitForm from '~/dirty_submit/dirty_submit_form';
+import DirtySubmitCollection from '~/dirty_submit/dirty_submit_collection';
+import { createForm } from './helper';
+
+describe('DirtySubmitCollection', () => {
+ it('returns a DirtySubmitForm instance for single form elements', () => {
+ const { form } = createForm();
+
+ expect(dirtySubmitFactory(form) instanceof DirtySubmitForm).toBe(true);
+ });
+
+ it('returns a DirtySubmitCollection instance for a collection of form elements', () => {
+ const forms = [createForm().form, createForm().form];
+
+ expect(dirtySubmitFactory(forms) instanceof DirtySubmitCollection).toBe(true);
+ });
+});
diff --git a/spec/javascripts/dirty_submit/dirty_submit_form_spec.js b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js
new file mode 100644
index 00000000000..86d53fa984a
--- /dev/null
+++ b/spec/javascripts/dirty_submit/dirty_submit_form_spec.js
@@ -0,0 +1,21 @@
+
+import DirtySubmitForm from '~/dirty_submit/dirty_submit_form';
+import { setInput, createForm } from './helper';
+
+describe('DirtySubmitForm', () => {
+ it('disables submit until there are changes', done => {
+ const { form, input, submit } = createForm();
+ const originalValue = input.value;
+
+ new DirtySubmitForm(form); // eslint-disable-line no-new
+
+ expect(submit.disabled).toBe(true);
+
+ return setInput(input, `${originalValue} changes`)
+ .then(() => expect(submit.disabled).toBe(false))
+ .then(() => setInput(input, originalValue))
+ .then(() => expect(submit.disabled).toBe(true))
+ .then(done)
+ .catch(done.fail);
+ });
+});
diff --git a/spec/javascripts/dirty_submit/helper.js b/spec/javascripts/dirty_submit/helper.js
new file mode 100644
index 00000000000..6d1e643553c
--- /dev/null
+++ b/spec/javascripts/dirty_submit/helper.js
@@ -0,0 +1,31 @@
+import DirtySubmitForm from '~/dirty_submit/dirty_submit_form';
+import setTimeoutPromiseHelper from '../helpers/set_timeout_promise_helper';
+
+export function setInput(element, value) {
+ element.value = value;
+
+ element.dispatchEvent(
+ new Event('input', {
+ bubbles: true,
+ cancelable: true,
+ }),
+ );
+
+ return setTimeoutPromiseHelper(DirtySubmitForm.THROTTLE_DURATION);
+}
+
+export function createForm() {
+ const form = document.createElement('form');
+ form.innerHTML = `
+ <input type="text" value="original" class="js-input" name="input" />
+ <button type="submit" class="js-dirty-submit"></button>
+ `;
+ const input = form.querySelector('.js-input');
+ const submit = form.querySelector('.js-dirty-submit');
+
+ return {
+ form,
+ input,
+ submit,
+ };
+}
diff --git a/spec/javascripts/emoji_spec.js b/spec/javascripts/emoji_spec.js
index 124d91f4477..629422780e8 100644
--- a/spec/javascripts/emoji_spec.js
+++ b/spec/javascripts/emoji_spec.js
@@ -140,6 +140,7 @@ describe('gl_emoji', () => {
},
);
});
+
it('bomb emoji with sprite fallback', () => {
const emojiKey = 'bomb';
const markup = glEmojiTag(emojiFixtureMap[emojiKey].name, {
@@ -195,24 +196,31 @@ describe('gl_emoji', () => {
it('should gracefully handle empty string', () => {
expect(isFlagEmoji('')).toBeFalsy();
});
+
it('should detect flag_ac', () => {
expect(isFlagEmoji('🇦🇨')).toBeTruthy();
});
+
it('should detect flag_us', () => {
expect(isFlagEmoji('🇺🇸')).toBeTruthy();
});
+
it('should detect flag_zw', () => {
expect(isFlagEmoji('🇿🇼')).toBeTruthy();
});
+
it('should not detect flags', () => {
expect(isFlagEmoji('ðŸŽ')).toBeFalsy();
});
+
it('should not detect triangular_flag_on_post', () => {
expect(isFlagEmoji('🚩')).toBeFalsy();
});
+
it('should not detect single letter', () => {
expect(isFlagEmoji('🇦')).toBeFalsy();
});
+
it('should not detect >2 letters', () => {
expect(isFlagEmoji('🇦🇧🇨')).toBeFalsy();
});
@@ -222,15 +230,19 @@ describe('gl_emoji', () => {
it('should gracefully handle empty string', () => {
expect(isRainbowFlagEmoji('')).toBeFalsy();
});
+
it('should detect rainbow_flag', () => {
expect(isRainbowFlagEmoji('ðŸ³ðŸŒˆ')).toBeTruthy();
});
+
it('should not detect flag_white on its\' own', () => {
expect(isRainbowFlagEmoji('ðŸ³')).toBeFalsy();
});
+
it('should not detect rainbow on its\' own', () => {
expect(isRainbowFlagEmoji('🌈')).toBeFalsy();
});
+
it('should not detect flag_white with something else', () => {
expect(isRainbowFlagEmoji('ðŸ³ðŸ”µ')).toBeFalsy();
});
@@ -240,15 +252,19 @@ describe('gl_emoji', () => {
it('should gracefully handle empty string', () => {
expect(isKeycapEmoji('')).toBeFalsy();
});
+
it('should detect one(keycap)', () => {
expect(isKeycapEmoji('1ï¸âƒ£')).toBeTruthy();
});
+
it('should detect nine(keycap)', () => {
expect(isKeycapEmoji('9ï¸âƒ£')).toBeTruthy();
});
+
it('should not detect ten(keycap)', () => {
expect(isKeycapEmoji('🔟')).toBeFalsy();
});
+
it('should not detect hash(keycap)', () => {
expect(isKeycapEmoji('#⃣')).toBeFalsy();
});
@@ -258,24 +274,31 @@ describe('gl_emoji', () => {
it('should gracefully handle empty string', () => {
expect(isSkinToneComboEmoji('')).toBeFalsy();
});
+
it('should detect hand_splayed_tone5', () => {
expect(isSkinToneComboEmoji('ðŸ–ðŸ¿')).toBeTruthy();
});
+
it('should not detect hand_splayed', () => {
expect(isSkinToneComboEmoji('ðŸ–')).toBeFalsy();
});
+
it('should detect lifter_tone1', () => {
expect(isSkinToneComboEmoji('ðŸ‹ðŸ»')).toBeTruthy();
});
+
it('should not detect lifter', () => {
expect(isSkinToneComboEmoji('ðŸ‹')).toBeFalsy();
});
+
it('should detect rowboat_tone4', () => {
expect(isSkinToneComboEmoji('🚣ðŸ¾')).toBeTruthy();
});
+
it('should not detect rowboat', () => {
expect(isSkinToneComboEmoji('🚣')).toBeFalsy();
});
+
it('should not detect individual tone emoji', () => {
expect(isSkinToneComboEmoji('ðŸ»')).toBeFalsy();
});
@@ -285,9 +308,11 @@ describe('gl_emoji', () => {
it('should gracefully handle empty string', () => {
expect(isHorceRacingSkinToneComboEmoji('')).toBeFalsy();
});
+
it('should detect horse_racing_tone2', () => {
expect(isHorceRacingSkinToneComboEmoji('ðŸ‡ðŸ¼')).toBeTruthy();
});
+
it('should not detect horse_racing', () => {
expect(isHorceRacingSkinToneComboEmoji('ðŸ‡')).toBeFalsy();
});
@@ -297,36 +322,47 @@ describe('gl_emoji', () => {
it('should gracefully handle empty string', () => {
expect(isPersonZwjEmoji('')).toBeFalsy();
});
+
it('should detect couple_mm', () => {
expect(isPersonZwjEmoji('👨â€â¤ï¸â€ðŸ‘¨')).toBeTruthy();
});
+
it('should not detect couple_with_heart', () => {
expect(isPersonZwjEmoji('💑')).toBeFalsy();
});
+
it('should not detect couplekiss', () => {
expect(isPersonZwjEmoji('ðŸ’')).toBeFalsy();
});
+
it('should detect family_mmb', () => {
expect(isPersonZwjEmoji('👨â€ðŸ‘¨â€ðŸ‘¦')).toBeTruthy();
});
+
it('should detect family_mwgb', () => {
expect(isPersonZwjEmoji('👨â€ðŸ‘©â€ðŸ‘§â€ðŸ‘¦')).toBeTruthy();
});
+
it('should not detect family', () => {
expect(isPersonZwjEmoji('👪')).toBeFalsy();
});
+
it('should detect kiss_ww', () => {
expect(isPersonZwjEmoji('👩â€â¤ï¸â€ðŸ’‹â€ðŸ‘©')).toBeTruthy();
});
+
it('should not detect girl', () => {
expect(isPersonZwjEmoji('👧')).toBeFalsy();
});
+
it('should not detect girl_tone5', () => {
expect(isPersonZwjEmoji('👧ðŸ¿')).toBeFalsy();
});
+
it('should not detect man', () => {
expect(isPersonZwjEmoji('👨')).toBeFalsy();
});
+
it('should not detect woman', () => {
expect(isPersonZwjEmoji('👩')).toBeFalsy();
});
@@ -341,6 +377,7 @@ describe('gl_emoji', () => {
);
expect(isSupported).toBeTruthy();
});
+
it('should gracefully handle empty string without unicode support', () => {
const isSupported = isEmojiUnicodeSupported(
{},
@@ -349,6 +386,7 @@ describe('gl_emoji', () => {
);
expect(isSupported).toBeFalsy();
});
+
it('bomb(6.0) with 6.0 support', () => {
const emojiKey = 'bomb';
const unicodeSupportMap = Object.assign({}, emptySupportMap, {
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index 6968fbc7ce7..60787b4c88d 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -158,12 +158,7 @@ describe('Environment', () => {
component.$el.querySelector('.folder-name').click();
Vue.nextTick(() => {
- expect(
- component.$el.querySelector('.folder-icon i.fa-caret-right').getAttribute('style'),
- ).toContain('display: none');
- expect(
- component.$el.querySelector('.folder-icon i.fa-caret-down').getAttribute('style'),
- ).not.toContain('display: none');
+ expect(component.$el.querySelector('.folder-icon.ic-chevron-right')).toBe(null);
done();
});
}, 0);
@@ -179,12 +174,7 @@ describe('Environment', () => {
component.$el.querySelector('.folder-name').click();
Vue.nextTick(() => {
- expect(
- component.$el.querySelector('.folder-icon i.fa-caret-down').getAttribute('style'),
- ).toContain('display: none');
- expect(
- component.$el.querySelector('.folder-icon i.fa-caret-right').getAttribute('style'),
- ).not.toContain('display: none');
+ expect(component.$el.querySelector('.folder-icon.ic-chevron-down')).toBe(null);
done();
});
});
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index 68bbbf838da..3dc8089cd83 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
@@ -237,7 +237,7 @@ describe('Dropdown Utils', () => {
it('should not linear-gradient more than 4 colors', () => {
const gradient = DropdownUtils.duplicateLabelColor(['#FFFFFF', '#000000', '#333333', '#DDDDDD', '#EEEEEE']);
- expect(gradient.indexOf('#EEEEEE') === -1).toEqual(true);
+ expect(gradient.indexOf('#EEEEEE')).toBe(-1);
});
});
diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
index ab0ab72720e..cf7789a1d57 100644
--- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
@@ -19,7 +19,7 @@ describe('Filtered Search Token Keys', () => {
describe('get', () => {
it('should return tokenKeys', () => {
- expect(new FilteredSearchTokenKeys().get() !== null).toBe(true);
+ expect(new FilteredSearchTokenKeys().get()).not.toBeNull();
});
it('should return tokenKeys as an array', () => {
@@ -40,7 +40,7 @@ describe('Filtered Search Token Keys', () => {
describe('getConditions', () => {
it('should return conditions', () => {
- expect(new FilteredSearchTokenKeys().getConditions() !== null).toBe(true);
+ expect(new FilteredSearchTokenKeys().getConditions()).not.toBeNull();
});
it('should return conditions as an array', () => {
@@ -51,7 +51,7 @@ describe('Filtered Search Token Keys', () => {
describe('searchByKey', () => {
it('should return null when key not found', () => {
const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchByKey('notakey');
- expect(tokenKey === null).toBe(true);
+ expect(tokenKey).toBeNull();
});
it('should return tokenKey when found by key', () => {
@@ -63,7 +63,7 @@ describe('Filtered Search Token Keys', () => {
describe('searchBySymbol', () => {
it('should return null when symbol not found', () => {
const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchBySymbol('notasymbol');
- expect(tokenKey === null).toBe(true);
+ expect(tokenKey).toBeNull();
});
it('should return tokenKey when found by symbol', () => {
@@ -75,7 +75,7 @@ describe('Filtered Search Token Keys', () => {
describe('searchByKeyParam', () => {
it('should return null when key param not found', () => {
const tokenKey = new FilteredSearchTokenKeys(tokenKeys).searchByKeyParam('notakeyparam');
- expect(tokenKey === null).toBe(true);
+ expect(tokenKey).toBeNull();
});
it('should return tokenKey when found by key param', () => {
@@ -92,7 +92,7 @@ describe('Filtered Search Token Keys', () => {
describe('searchByConditionUrl', () => {
it('should return null when condition url not found', () => {
const condition = new FilteredSearchTokenKeys([], [], conditions).searchByConditionUrl(null);
- expect(condition === null).toBe(true);
+ expect(condition).toBeNull();
});
it('should return condition when found by url', () => {
@@ -106,7 +106,7 @@ describe('Filtered Search Token Keys', () => {
it('should return null when condition tokenKey and value not found', () => {
const condition = new FilteredSearchTokenKeys([], [], conditions)
.searchByConditionKeyValue(null, null);
- expect(condition === null).toBe(true);
+ expect(condition).toBeNull();
});
it('should return condition when found by tokenKey and value', () => {
diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb
index a2035ceae15..b42f442557c 100644
--- a/spec/javascripts/fixtures/groups.rb
+++ b/spec/javascripts/fixtures/groups.rb
@@ -17,6 +17,16 @@ describe 'Groups (JavaScript fixtures)', type: :controller do
sign_in(admin)
end
+ describe GroupsController, '(JavaScript fixtures)', type: :controller do
+ it 'groups/edit.html.raw' do |example|
+ get :edit,
+ id: group
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+ end
+
describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
it 'groups/ci_cd_settings.html.raw' do |example|
get :show,
diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js
index 25b819543da..62c87642184 100644
--- a/spec/javascripts/gl_dropdown_spec.js
+++ b/spec/javascripts/gl_dropdown_spec.js
@@ -168,9 +168,9 @@ describe('glDropdown', function describeDropdown() {
it('should show loading indicator while search results are being fetched by backend', () => {
const dropdownMenu = document.querySelector('.dropdown-menu');
- expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(true);
+ expect(dropdownMenu.className.indexOf('is-loading')).not.toBe(-1);
remoteCallback();
- expect(dropdownMenu.className.indexOf('is-loading') !== -1).toEqual(false);
+ expect(dropdownMenu.className.indexOf('is-loading')).toBe(-1);
});
it('should not focus search input while remote task is not complete', () => {
diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
index 02d1ca1cc3b..8854d3d554a 100644
--- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
+++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js
@@ -205,10 +205,12 @@ describe("ContributorsStatGraphUtil", function () {
it("returns true if date_range is null", function () {
expect(ContributorsStatGraphUtil.in_range(date, null)).toEqual(true);
});
+
it("returns true if date is in range", function () {
var date_range = [new Date("2013-01-01"), new Date("2013-12-12")];
expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(true);
});
+
it("returns false if date is not in range", function () {
var date_range = [new Date("1999-12-01"), new Date("2000-12-01")];
expect(ContributorsStatGraphUtil.in_range(date, date_range)).toEqual(false);
diff --git a/spec/javascripts/groups/components/group_item_spec.js b/spec/javascripts/groups/components/group_item_spec.js
index d0cac5efc40..49d4f7efd72 100644
--- a/spec/javascripts/groups/components/group_item_spec.js
+++ b/spec/javascripts/groups/components/group_item_spec.js
@@ -45,7 +45,7 @@ describe('GroupItemComponent', () => {
expect(Object.keys(rowClass).length).toBe(classes.length);
Object.keys(rowClass).forEach((className) => {
- expect(classes.indexOf(className) > -1).toBeTruthy();
+ expect(classes.indexOf(className)).toBeGreaterThan(-1);
});
});
});
diff --git a/spec/javascripts/groups/components/groups_spec.js b/spec/javascripts/groups/components/groups_spec.js
index 793c4909d89..5af86b55532 100644
--- a/spec/javascripts/groups/components/groups_spec.js
+++ b/spec/javascripts/groups/components/groups_spec.js
@@ -53,7 +53,7 @@ describe('GroupsComponent', () => {
expect(vm.$el.querySelector('.groups-list-tree-container')).toBeDefined();
expect(vm.$el.querySelector('.group-list-tree')).toBeDefined();
expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
- expect(vm.$el.querySelectorAll('.has-no-search-results').length === 0).toBeTruthy();
+ expect(vm.$el.querySelectorAll('.has-no-search-results').length).toBe(0);
done();
});
});
diff --git a/spec/javascripts/groups/components/item_stats_spec.js b/spec/javascripts/groups/components/item_stats_spec.js
index ee7ee18259e..e6a57495eb1 100644
--- a/spec/javascripts/groups/components/item_stats_spec.js
+++ b/spec/javascripts/groups/components/item_stats_spec.js
@@ -107,7 +107,7 @@ describe('ItemStatsComponent', () => {
const visibilityIconEl = vm.$el.querySelector('.item-visibility');
expect(visibilityIconEl).not.toBe(null);
expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip);
- expect(visibilityIconEl.querySelectorAll('svg').length > 0).toBeTruthy();
+ expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
vm.$destroy();
});
@@ -120,10 +120,10 @@ describe('ItemStatsComponent', () => {
const vm = createComponent(item);
const projectStarIconEl = vm.$el.querySelector('.project-stars');
- expect(projectStarIconEl).not.toBe(null);
- expect(projectStarIconEl.querySelectorAll('svg').length > 0).toBeTruthy();
- expect(projectStarIconEl.querySelectorAll('.stat-value').length > 0).toBeTruthy();
- expect(vm.$el.querySelectorAll('.last-updated').length > 0).toBeTruthy();
+ expect(projectStarIconEl).not.toBeNull();
+ expect(projectStarIconEl.querySelectorAll('svg').length).toBeGreaterThan(0);
+ expect(projectStarIconEl.querySelectorAll('.stat-value').length).toBeGreaterThan(0);
+ expect(vm.$el.querySelectorAll('.last-updated').length).toBeGreaterThan(0);
vm.$destroy();
});
diff --git a/spec/javascripts/groups/components/item_stats_value_spec.js b/spec/javascripts/groups/components/item_stats_value_spec.js
index 5e35ae4d36c..2a995e8efe6 100644
--- a/spec/javascripts/groups/components/item_stats_value_spec.js
+++ b/spec/javascripts/groups/components/item_stats_value_spec.js
@@ -57,8 +57,8 @@ describe('ItemStatsValueComponent', () => {
it('renders component element correctly', () => {
expect(vm.$el.classList.contains('number-subgroups')).toBeTruthy();
- expect(vm.$el.querySelectorAll('svg').length > 0).toBeTruthy();
- expect(vm.$el.querySelectorAll('.stat-value').length > 0).toBeTruthy();
+ expect(vm.$el.querySelectorAll('svg').length).toBeGreaterThan(0);
+ expect(vm.$el.querySelectorAll('.stat-value').length).toBeGreaterThan(0);
});
it('renders element tooltip correctly', () => {
diff --git a/spec/javascripts/groups/store/groups_store_spec.js b/spec/javascripts/groups/store/groups_store_spec.js
index d74f38f476e..78caf8f80bf 100644
--- a/spec/javascripts/groups/store/groups_store_spec.js
+++ b/spec/javascripts/groups/store/groups_store_spec.js
@@ -29,7 +29,7 @@ describe('ProjectsStore', () => {
store.setGroups(mockGroups);
expect(store.state.groups.length).toBe(mockGroups.length);
expect(store.formatGroupItem).toHaveBeenCalledWith(jasmine.any(Object));
- expect(Object.keys(store.state.groups[0]).indexOf('fullName') > -1).toBeTruthy();
+ expect(Object.keys(store.state.groups[0]).indexOf('fullName')).toBeGreaterThan(-1);
});
});
@@ -41,8 +41,8 @@ describe('ProjectsStore', () => {
store.setSearchedGroups(mockSearchedGroups);
expect(store.state.groups.length).toBe(mockSearchedGroups.length);
expect(store.formatGroupItem).toHaveBeenCalledWith(jasmine.any(Object));
- expect(Object.keys(store.state.groups[0]).indexOf('fullName') > -1).toBeTruthy();
- expect(Object.keys(store.state.groups[0].children[0]).indexOf('fullName') > -1).toBeTruthy();
+ expect(Object.keys(store.state.groups[0]).indexOf('fullName')).toBeGreaterThan(-1);
+ expect(Object.keys(store.state.groups[0].children[0]).indexOf('fullName')).toBeGreaterThan(-1);
});
});
@@ -54,7 +54,7 @@ describe('ProjectsStore', () => {
store.setGroupChildren(mockParentGroupItem, mockRawChildren);
expect(store.formatGroupItem).toHaveBeenCalledWith(jasmine.any(Object));
expect(mockParentGroupItem.children.length).toBe(1);
- expect(Object.keys(mockParentGroupItem.children[0]).indexOf('fullName') > -1).toBeTruthy();
+ expect(Object.keys(mockParentGroupItem.children[0]).indexOf('fullName')).toBeGreaterThan(-1);
expect(mockParentGroupItem.isOpen).toBeTruthy();
expect(mockParentGroupItem.isChildrenLoading).toBeFalsy();
});
@@ -81,14 +81,14 @@ describe('ProjectsStore', () => {
store = new GroupsStore();
updatedGroupItem = store.formatGroupItem(mockRawChildren[0]);
- expect(Object.keys(updatedGroupItem).indexOf('fullName') > -1).toBeTruthy();
+ expect(Object.keys(updatedGroupItem).indexOf('fullName')).toBeGreaterThan(-1);
expect(updatedGroupItem.childrenCount).toBe(mockRawChildren[0].children_count);
expect(updatedGroupItem.isChildrenLoading).toBe(false);
expect(updatedGroupItem.isBeingRemoved).toBe(false);
store = new GroupsStore(true);
updatedGroupItem = store.formatGroupItem(mockRawChildren[0]);
- expect(Object.keys(updatedGroupItem).indexOf('fullName') > -1).toBeTruthy();
+ expect(Object.keys(updatedGroupItem).indexOf('fullName')).toBeGreaterThan(-1);
expect(updatedGroupItem.childrenCount).toBe(mockRawChildren[0].subgroup_count);
});
});
diff --git a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js b/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js
index a51527d699f..8e0e3ae99a1 100644
--- a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js
+++ b/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js
@@ -49,6 +49,14 @@ describe('IDE file templates mutations', () => {
expect(state.selectedTemplateType).toBe('type');
});
+
+ it('clears templates', () => {
+ state.templates = ['test'];
+
+ mutations[types.SET_SELECTED_TEMPLATE_TYPE](state, 'type');
+
+ expect(state.templates).toEqual([]);
+ });
});
describe(types.SET_UPDATE_SUCCESS, () => {
diff --git a/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js
index a284b981d2a..9d05c6859df 100644
--- a/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js
+++ b/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js
@@ -32,7 +32,7 @@ describe('commentIndicatorHelper', () => {
expect(svgEl).toBeDefined();
const svgLink = svgEl.querySelector('use').getAttribute('xlink:href');
- expect(svgLink.indexOf('image-comment-dark') !== -1).toEqual(true);
+ expect(svgLink.indexOf('image-comment-dark')).not.toBe(-1);
});
});
});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index e02eb9723fe..6e0bcf801cd 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -41,7 +41,7 @@ describe('Job App ', () => {
};
const props = {
- runnerHelpUrl: 'help/runners',
+ runnerSettingsUrl: 'settings/ci-cd/runners',
};
beforeEach(() => {
@@ -223,7 +223,6 @@ describe('Job App ', () => {
store.dispatch(
'receiveJobSuccess',
Object.assign({}, job, {
- erased: true,
erased_by: {
username: 'root',
web_url: 'gitlab.com/root',
@@ -237,18 +236,18 @@ describe('Job App ', () => {
store,
});
- expect(vm.$el.querySelector('.js-job-erased')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull();
});
it('does not render erased block when `erased` is false', () => {
- store.dispatch('receiveJobSuccess', Object.assign({}, job, { erased: false }));
+ store.dispatch('receiveJobSuccess', Object.assign({}, job, { erased_at: null }));
vm = mountComponentWithStore(Component, {
props,
store,
});
- expect(vm.$el.querySelector('.js-job-erased')).toBeNull();
+ expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull();
});
});
diff --git a/spec/javascripts/jobs/components/job_log_controllers_spec.js b/spec/javascripts/jobs/components/job_log_controllers_spec.js
index 099aca602c4..3dcb57d23ce 100644
--- a/spec/javascripts/jobs/components/job_log_controllers_spec.js
+++ b/spec/javascripts/jobs/components/job_log_controllers_spec.js
@@ -25,6 +25,7 @@ describe('Job log controllers', () => {
beforeEach(() => {
vm = mountComponent(Component, props);
});
+
it('renders size information', () => {
expect(vm.$el.querySelector('.js-truncated-info').textContent).toContain('499.95 KiB');
});
diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js
index 2f5c4245ced..a113377b19f 100644
--- a/spec/javascripts/jobs/components/sidebar_spec.js
+++ b/spec/javascripts/jobs/components/sidebar_spec.js
@@ -161,9 +161,9 @@ describe('Sidebar details block', () => {
vm = mountComponentWithStore(SidebarComponent, { store });
});
- it('renders first stage as selected', () => {
+ it('renders value provided as selectedStage as selected', () => {
expect(vm.$el.querySelector('.js-selected-stage').textContent.trim()).toEqual(
- stages[0].name,
+ vm.selectedStage,
);
});
});
diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js
index aa6cc0f1b1a..fcff78b943e 100644
--- a/spec/javascripts/jobs/components/stages_dropdown_spec.js
+++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js
@@ -2,7 +2,7 @@ import Vue from 'vue';
import component from '~/jobs/components/stages_dropdown.vue';
import mountComponent from '../../helpers/vue_mount_component_helper';
-describe('Artifacts block', () => {
+describe('Stages Dropdown', () => {
const Component = Vue.extend(component);
let vm;
@@ -23,10 +23,6 @@ describe('Artifacts block', () => {
},
path: 'pipeline/28029444',
},
- ref: {
- path: 'commits/50101-truncated-job-information',
- name: '50101-truncated-job-information',
- },
stages: [
{
name: 'build',
@@ -35,6 +31,7 @@ describe('Artifacts block', () => {
name: 'test',
},
],
+ selectedStage: 'deploy'
});
});
@@ -53,17 +50,10 @@ describe('Artifacts block', () => {
});
it('renders dropdown with stages', () => {
- expect(vm.$el.querySelector('.dropdown button').textContent).toContain('build');
+ expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build');
});
- it('updates selected stage on click', done => {
- vm.$el.querySelectorAll('.stage-item')[1].click();
- vm
- .$nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.dropdown button').textContent).toContain('test');
- })
- .then(done)
- .catch(done.fail);
+ it('rendes selected stage', () => {
+ expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
});
});
diff --git a/spec/javascripts/jobs/store/actions_spec.js b/spec/javascripts/jobs/store/actions_spec.js
index 5ab1f75d0d6..bc410ae614c 100644
--- a/spec/javascripts/jobs/store/actions_spec.js
+++ b/spec/javascripts/jobs/store/actions_spec.js
@@ -422,8 +422,9 @@ describe('Job State actions', () => {
beforeEach(() => {
mockedState.job.pipeline = {
- path: `${TEST_HOST}/endpoint.json/stages`,
+ path: `${TEST_HOST}/endpoint`,
};
+ mockedState.selectedStage = 'deploy'
mock = new MockAdapter(axios);
});
@@ -434,8 +435,8 @@ describe('Job State actions', () => {
describe('success', () => {
it('dispatches requestStages and receiveStagesSuccess, fetchJobsForStage ', done => {
mock
- .onGet(`${TEST_HOST}/endpoint.json/stages`)
- .replyOnce(200, { details: { stages: [{ id: 121212, name: 'build' }] } });
+ .onGet(`${TEST_HOST}/endpoint.json`)
+ .replyOnce(200, { details: { stages: [{ name: 'build' }, { name: 'deploy' }] } });
testAction(
fetchStages,
@@ -447,11 +448,11 @@ describe('Job State actions', () => {
type: 'requestStages',
},
{
- payload: [{ id: 121212, name: 'build' }],
+ payload: [{ name: 'build' }, { name: 'deploy' }],
type: 'receiveStagesSuccess',
},
{
- payload: { id: 121212, name: 'build' },
+ payload: { name: 'deploy' },
type: 'fetchJobsForStage',
},
],
@@ -515,9 +516,9 @@ describe('Job State actions', () => {
it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => {
testAction(
requestJobsForStage,
- null,
+ { name: 'deploy' },
mockedState,
- [{ type: types.REQUEST_JOBS_FOR_STAGE }],
+ [{ type: types.REQUEST_JOBS_FOR_STAGE, payload: { name: 'deploy' } }],
[],
done,
);
@@ -549,6 +550,7 @@ describe('Job State actions', () => {
[
{
type: 'requestJobsForStage',
+ payload: { dropdown_path: `${TEST_HOST}/jobs.json` },
},
{
payload: [{ id: 121212, name: 'build' }],
@@ -574,6 +576,7 @@ describe('Job State actions', () => {
[
{
type: 'requestJobsForStage',
+ payload: { dropdown_path: `${TEST_HOST}/jobs.json` },
},
{
type: 'receiveJobsForStageError',
diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js
index 160b2f4b34a..e262a47b837 100644
--- a/spec/javascripts/jobs/store/getters_spec.js
+++ b/spec/javascripts/jobs/store/getters_spec.js
@@ -77,18 +77,18 @@ describe('Job Store Getters', () => {
});
});
- describe('jobHasStarted', () => {
- describe('when started equals false', () => {
+ describe('shouldRenderTriggeredLabel', () => {
+ describe('when started equals null', () => {
it('returns false', () => {
- localState.job.started = false;
- expect(getters.jobHasStarted(localState)).toEqual(false);
+ localState.job.started = null;
+ expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(false);
});
});
describe('when started equals string', () => {
it('returns true', () => {
localState.job.started = '2018-08-31T16:20:49.023Z';
- expect(getters.jobHasStarted(localState)).toEqual(true);
+ expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(true);
});
});
});
diff --git a/spec/javascripts/jobs/store/mutations_spec.js b/spec/javascripts/jobs/store/mutations_spec.js
index 9ba543d32a8..701fcc7f4c8 100644
--- a/spec/javascripts/jobs/store/mutations_spec.js
+++ b/spec/javascripts/jobs/store/mutations_spec.js
@@ -108,21 +108,33 @@ describe('Jobs Store Mutations', () => {
});
describe('RECEIVE_JOB_SUCCESS', () => {
- beforeEach(() => {
- mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
- });
-
it('sets is loading to false', () => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
expect(stateCopy.isLoading).toEqual(false);
});
it('sets hasError to false', () => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
expect(stateCopy.hasError).toEqual(false);
});
it('sets job data', () => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
expect(stateCopy.job).toEqual({ id: 1312321 });
});
+
+ it('sets selectedStage when the selectedStage is More', () => {
+ expect(stateCopy.selectedStage).toEqual('More');
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
+ expect(stateCopy.selectedStage).toEqual('deploy');
+ });
+
+ it('does not set selectedStage when the selectedStage is not More', () => {
+ stateCopy.selectedStage = 'notify'
+ expect(stateCopy.selectedStage).toEqual('notify');
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
+ expect(stateCopy.selectedStage).toEqual('notify');
+ });
});
describe('RECEIVE_JOB_ERROR', () => {
@@ -200,9 +212,14 @@ describe('Jobs Store Mutations', () => {
describe('REQUEST_JOBS_FOR_STAGE', () => {
it('sets isLoadingStages to true', () => {
- mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy);
+ mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
expect(stateCopy.isLoadingJobs).toEqual(true);
});
+
+ it('sets selectedStage', () => {
+ mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
+ expect(stateCopy.selectedStage).toEqual('deploy');
+ })
});
describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => {
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 28151c7e658..b2b8f64d705 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -1,4 +1,3 @@
-/* eslint-disable promise/catch-or-return */
import axios from '~/lib/utils/axios_utils';
import * as commonUtils from '~/lib/utils/common_utils';
import MockAdapter from 'axios-mock-adapter';
@@ -9,6 +8,7 @@ describe('common_utils', () => {
it('returns an anchor tag with url', () => {
expect(commonUtils.parseUrl('/some/absolute/url').pathname).toContain('some/absolute/url');
});
+
it('url is escaped', () => {
// IE11 will return a relative pathname while other browsers will return a full pathname.
// parseUrl uses an anchor element for parsing an url. With relative urls, the anchor
@@ -42,7 +42,7 @@ describe('common_utils', () => {
it('should remove the question mark from the search params', () => {
const paramsArray = commonUtils.urlParamsToArray('?test=thing');
- expect(paramsArray[0][0] !== '?').toBe(true);
+ expect(paramsArray[0][0]).not.toBe('?');
});
});
@@ -359,10 +359,10 @@ describe('common_utils', () => {
}).then((resp) => {
stop(resp);
})
- )).then((respBackoff) => {
+ ).catch(done.fail)).then((respBackoff) => {
expect(respBackoff).toBe(expectedResponseValue);
done();
- });
+ }).catch(done.fail);
});
it('catches the rejected promise from the callback ', (done) => {
@@ -393,12 +393,12 @@ describe('common_utils', () => {
stop(resp);
}
})
- )).then((respBackoff) => {
+ ).catch(done.fail)).then((respBackoff) => {
const timeouts = window.setTimeout.calls.allArgs().map(([, timeout]) => timeout);
expect(timeouts).toEqual([2000, 4000]);
expect(respBackoff).toBe(expectedResponseValue);
done();
- });
+ }).catch(done.fail);
});
it('rejects the backOff promise after timing out', (done) => {
@@ -459,7 +459,7 @@ describe('common_utils', () => {
commonUtils.createOverlayIcon(faviconDataUrl, overlayDataUrl).then((url) => {
expect(url).toEqual(faviconWithOverlayDataUrl);
done();
- });
+ }).catch(done.fail);
});
});
@@ -479,7 +479,7 @@ describe('common_utils', () => {
commonUtils.setFaviconOverlay(overlayDataUrl).then(() => {
expect(document.getElementById('favicon').getAttribute('href')).toEqual(faviconWithOverlayDataUrl);
done();
- });
+ }).catch(done.fail);
});
});
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index c32ecb17e89..15bb78032b2 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -23,17 +23,20 @@ import LineHighlighter from '~/line_highlighter';
__setLocationHash__: spyOn(this["class"], '__setLocationHash__').and.callFake(function() {})
};
});
+
describe('behavior', function() {
it('highlights one line given in the URL hash', function() {
new LineHighlighter({ hash: '#L13' });
return expect($('#LC13')).toHaveClass(this.css);
});
+
it('highlights one line given in the URL hash with given CSS class name', function() {
const hiliter = new LineHighlighter({ hash: '#L13', highlightLineClass: 'hilite' });
expect(hiliter.highlightLineClass).toBe('hilite');
expect($('#LC13')).toHaveClass('hilite');
expect($('#LC13')).not.toHaveClass('hll');
});
+
it('highlights a range of lines given in the URL hash', function() {
var line, results;
new LineHighlighter({ hash: '#L5-25' });
@@ -44,18 +47,21 @@ import LineHighlighter from '~/line_highlighter';
}
return results;
});
+
it('scrolls to the first highlighted line on initial load', function() {
var spy;
spy = spyOn($, 'scrollTo');
new LineHighlighter({ hash: '#L5-25' });
return expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything());
});
+
it('discards click events', function() {
var spy;
spy = spyOnEvent('a[data-line-number]', 'click');
clickLine(13);
return expect(spy).toHaveBeenPrevented();
});
+
it('handles garbage input from the hash', function() {
var func;
func = function() {
@@ -64,6 +70,7 @@ import LineHighlighter from '~/line_highlighter';
return expect(func).not.toThrow();
});
});
+
describe('clickHandler', function() {
it('handles clicking on a child icon element', function() {
var spy;
@@ -72,11 +79,13 @@ import LineHighlighter from '~/line_highlighter';
expect(spy).toHaveBeenCalledWith(13);
return expect($('#LC13')).toHaveClass(this.css);
});
+
describe('without shiftKey', function() {
it('highlights one line when clicked', function() {
clickLine(13);
return expect($('#LC13')).toHaveClass(this.css);
});
+
it('unhighlights previously highlighted lines', function() {
clickLine(13);
clickLine(20);
@@ -101,6 +110,7 @@ import LineHighlighter from '~/line_highlighter';
expect(spy).toHaveBeenCalledWith(13);
return expect(spy).toHaveBeenCalledWith(13, 20);
});
+
describe('without existing highlight', function() {
it('highlights the clicked line', function() {
clickLine(13, {
@@ -118,6 +128,7 @@ import LineHighlighter from '~/line_highlighter';
return expect(spy).toHaveBeenCalledWith(13);
});
});
+
describe('with existing single-line highlight', function() {
it('uses existing line as last line when target is lesser', function() {
var line, results;
@@ -155,6 +166,7 @@ import LineHighlighter from '~/line_highlighter';
shiftKey: true
});
});
+
it('uses target as first line when it is less than existing first line', function() {
var line, results;
clickLine(5, {
@@ -182,13 +194,16 @@ import LineHighlighter from '~/line_highlighter';
});
});
});
+
describe('hashToRange', function() {
beforeEach(function() {
return this.subject = this["class"].hashToRange;
});
+
it('extracts a single line number from the hash', function() {
return expect(this.subject('#L5')).toEqual([5, null]);
});
+
it('extracts a range of line numbers from the hash', function() {
return expect(this.subject('#L5-15')).toEqual([5, 15]);
});
@@ -196,10 +211,12 @@ import LineHighlighter from '~/line_highlighter';
return expect(this.subject('#foo')).toEqual([null, null]);
});
});
+
describe('highlightLine', function() {
beforeEach(function() {
return this.subject = this["class"].highlightLine;
});
+
it('highlights the specified line', function() {
this.subject(13);
return expect($('#LC13')).toHaveClass(this.css);
@@ -213,6 +230,7 @@ import LineHighlighter from '~/line_highlighter';
beforeEach(function() {
return this.subject = this["class"].setHash;
});
+
it('sets the location hash for a single line', function() {
this.subject(5);
return expect(this.spies.__setLocationHash__).toHaveBeenCalledWith('#L5');
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index e52ac686435..85419e640d8 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -21,18 +21,22 @@ import NewBranchForm from '~/new_branch_form';
});
return this.form = new NewBranchForm($('.js-create-branch-form'), []);
});
+
it("can't start with a dot", function() {
fillNameWith('.foo');
return expectToHaveError("can't start with '.'");
});
+
it("can't start with a slash", function() {
fillNameWith('/foo');
return expectToHaveError("can't start with '/'");
});
+
it("can't have two consecutive dots", function() {
fillNameWith('foo..bar');
return expectToHaveError("can't contain '..'");
});
+
it("can't have spaces anywhere", function() {
fillNameWith(' foo');
expectToHaveError("can't contain spaces");
@@ -41,6 +45,7 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith('foo ');
return expectToHaveError("can't contain spaces");
});
+
it("can't have ~ anywhere", function() {
fillNameWith('~foo');
expectToHaveError("can't contain '~'");
@@ -49,6 +54,7 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith('foo~');
return expectToHaveError("can't contain '~'");
});
+
it("can't have tilde anwhere", function() {
fillNameWith('~foo');
expectToHaveError("can't contain '~'");
@@ -57,6 +63,7 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith('foo~');
return expectToHaveError("can't contain '~'");
});
+
it("can't have caret anywhere", function() {
fillNameWith('^foo');
expectToHaveError("can't contain '^'");
@@ -65,6 +72,7 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith('foo^');
return expectToHaveError("can't contain '^'");
});
+
it("can't have : anywhere", function() {
fillNameWith(':foo');
expectToHaveError("can't contain ':'");
@@ -73,6 +81,7 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith(':foo');
return expectToHaveError("can't contain ':'");
});
+
it("can't have question mark anywhere", function() {
fillNameWith('?foo');
expectToHaveError("can't contain '?'");
@@ -81,6 +90,7 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith('foo?');
return expectToHaveError("can't contain '?'");
});
+
it("can't have asterisk anywhere", function() {
fillNameWith('*foo');
expectToHaveError("can't contain '*'");
@@ -89,6 +99,7 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith('foo*');
return expectToHaveError("can't contain '*'");
});
+
it("can't have open bracket anywhere", function() {
fillNameWith('[foo');
expectToHaveError("can't contain '['");
@@ -97,6 +108,7 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith('foo[');
return expectToHaveError("can't contain '['");
});
+
it("can't have a backslash anywhere", function() {
fillNameWith('\\foo');
expectToHaveError("can't contain '\\'");
@@ -105,6 +117,7 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith('foo\\');
return expectToHaveError("can't contain '\\'");
});
+
it("can't contain a sequence @{ anywhere", function() {
fillNameWith('@{foo');
expectToHaveError("can't contain '@{'");
@@ -113,48 +126,59 @@ import NewBranchForm from '~/new_branch_form';
fillNameWith('foo@{');
return expectToHaveError("can't contain '@{'");
});
+
it("can't have consecutive slashes", function() {
fillNameWith('foo//bar');
return expectToHaveError("can't contain consecutive slashes");
});
+
it("can't end with a slash", function() {
fillNameWith('foo/');
return expectToHaveError("can't end in '/'");
});
+
it("can't end with a dot", function() {
fillNameWith('foo.');
return expectToHaveError("can't end in '.'");
});
+
it("can't end with .lock", function() {
fillNameWith('foo.lock');
return expectToHaveError("can't end in '.lock'");
});
+
it("can't be the single character @", function() {
fillNameWith('@');
return expectToHaveError("can't be '@'");
});
+
it("concatenates all error messages", function() {
fillNameWith('/foo bar?~.');
return expectToHaveError("can't start with '/', can't contain spaces, '?', '~', can't end in '.'");
});
+
it("doesn't duplicate error messages", function() {
fillNameWith('?foo?bar?zoo?');
return expectToHaveError("can't contain '?'");
});
+
it("removes the error message when is a valid name", function() {
fillNameWith('foo?bar');
expect($('.js-branch-name-error span').length).toEqual(1);
fillNameWith('foobar');
return expect($('.js-branch-name-error span').length).toEqual(0);
});
+
it("can have dashes anywhere", function() {
fillNameWith('-foo-bar-zoo-');
return expect($('.js-branch-name-error span').length).toEqual(0);
});
+
it("can have underscores anywhere", function() {
fillNameWith('_foo_bar_zoo_');
return expect($('.js-branch-name-error span').length).toEqual(0);
});
+
it("can have numbers anywhere", function() {
fillNameWith('1foo2bar3zoo4');
return expect($('.js-branch-name-error span').length).toEqual(0);
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index 147ffcf1b81..eefd9ddd63c 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -100,6 +100,7 @@ describe('issue_note_form component', () => {
expect(vm.handleUpdate).toHaveBeenCalled();
});
+
it('should save note when ctrl+enter is pressed', () => {
spyOn(vm, 'handleUpdate').and.callThrough();
vm.$el.querySelector('textarea').value = 'Foo';
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index faeedae40e9..7bfbca83c77 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -538,7 +538,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
mockNotesPost();
$('.js-comment-button').click();
- expect($notesContainer.find('.note.being-posted').length > 0).toEqual(true);
+ expect($notesContainer.find('.note.being-posted').length).toBeGreaterThan(0);
});
it('should remove placeholder note when new comment is done posting', done => {
@@ -582,7 +582,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
$('.js-comment-button').click();
setTimeout(() => {
- expect($notesContainer.find(`#note_${note.id}`).length > 0).toEqual(true);
+ expect($notesContainer.find(`#note_${note.id}`).length).toBeGreaterThan(0);
done();
});
@@ -776,7 +776,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper';
$form.find('textarea.js-note-text').val(sampleComment);
const { formData, formContent, formAction } = this.notes.getFormData($form);
- expect(formData.indexOf(sampleComment) > -1).toBe(true);
+ expect(formData.indexOf(sampleComment)).toBeGreaterThan(-1);
expect(formContent).toEqual(sampleComment);
expect(formAction).toEqual($form.attr('action'));
});
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 50141bd99b4..ab4c00a79ef 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -719,7 +719,7 @@ describe('Pipelines', () => {
expect(vm.poll.restart).toHaveBeenCalled();
done();
}, 0);
- });
+ }).catch(done.fail);
}, 0);
});
});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js
index 21805ef0b28..345a7c551d1 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown_spec.js
@@ -96,8 +96,8 @@ describe('GkeMachineTypeDropdown', () => {
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('input').value).toBe(selectedMachineTypeMock);
done();
- });
- });
+ }).catch(done.fail);
+ }).catch(done.fail);
});
});
});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
index d4fcb2dc8ff..3d1a0b87bb8 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown_spec.js
@@ -57,13 +57,13 @@ describe('GkeProjectIdDropdown', () => {
expect(vm.toggleText).toBe(LABELS.DEFAULT);
done();
- }));
+ }).catch(done.fail));
it('returns project name if project selected', done =>
vm.$nextTick().then(() => {
expect(vm.toggleText).toBe(selectedProjectMock.name);
done();
- }));
+ }).catch(done.fail));
it('returns empty toggle text', done =>
vm.$nextTick().then(() => {
@@ -72,7 +72,7 @@ describe('GkeProjectIdDropdown', () => {
expect(vm.toggleText).toBe(LABELS.EMPTY);
done();
- }));
+ }).catch(done.fail));
});
describe('selectItem', () => {
@@ -85,8 +85,8 @@ describe('GkeProjectIdDropdown', () => {
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('input').value).toBe(selectedProjectMock.projectId);
done();
- });
- });
+ }).catch(done.fail);
+ }).catch(done.fail);
});
});
});
diff --git a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js
index 89a4a7ea2ce..a07e00845cf 100644
--- a/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js
+++ b/spec/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown_spec.js
@@ -81,8 +81,8 @@ describe('GkeZoneDropdown', () => {
return vm.$nextTick().then(() => {
expect(vm.$el.querySelector('input').value).toBe(selectedZoneMock);
done();
- });
- });
+ }).catch(done.fail);
+ }).catch(done.fail);
});
});
});
diff --git a/spec/javascripts/reports/components/report_section_spec.js b/spec/javascripts/reports/components/report_section_spec.js
index 6f6eb161d14..bf11dbea386 100644
--- a/spec/javascripts/reports/components/report_section_spec.js
+++ b/spec/javascripts/reports/components/report_section_spec.js
@@ -86,6 +86,7 @@ describe('Report section', () => {
});
});
});
+
describe('when it is loading', () => {
it('should render loading indicator', () => {
vm = mountComponent(ReportSection, {
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index f9395eedfea..43e4db4c712 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-var, one-var, no-return-assign, vars-on-top, jasmine/no-unsafe-spy */
+/* eslint-disable no-var, one-var, no-return-assign, vars-on-top */
import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
@@ -60,10 +60,12 @@ import Sidebar from '~/right_sidebar';
$toggle.click();
assertSidebarState('expanded');
});
+
it('should float over the page and when sidebar icons clicked', function() {
$labelsIcon.click();
return assertSidebarState('expanded');
});
+
it('should collapse when the icon arrow clicked while it is floating on page', function() {
$labelsIcon.click();
assertSidebarState('expanded');
@@ -92,33 +94,5 @@ import Sidebar from '~/right_sidebar';
});
});
});
-
- describe('sidebarToggleClicked', () => {
- const event = jasmine.createSpyObj('event', ['preventDefault']);
-
- beforeEach(() => {
- spyOn($.fn, 'hasClass').and.returnValue(false);
- });
-
- afterEach(() => {
- gl.lazyLoader = undefined;
- });
-
- it('calls loadCheck if lazyLoader is set', () => {
- gl.lazyLoader = jasmine.createSpyObj('lazyLoader', ['loadCheck']);
-
- Sidebar.prototype.sidebarToggleClicked(event);
-
- expect(gl.lazyLoader.loadCheck).toHaveBeenCalled();
- });
-
- it('does not throw if lazyLoader is not defined', () => {
- gl.lazyLoader = undefined;
-
- const toggle = Sidebar.prototype.sidebarToggleClicked.bind(null, event);
-
- expect(toggle).not.toThrow();
- });
- });
});
}).call(window);
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index b96023a33c4..bc1bb50dc5c 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -140,6 +140,7 @@ describe('Search autocomplete dropdown', () => {
removeBodyAttributes();
window.gon = {};
});
+
it('should show Dashboard specific dropdown menu', function() {
var list;
addBodyAttributes();
@@ -148,6 +149,7 @@ describe('Search autocomplete dropdown', () => {
list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, dashboardIssuesPath, dashboardMRsPath);
});
+
it('should show Group specific dropdown menu', function() {
var list;
addBodyAttributes('group');
@@ -156,6 +158,7 @@ describe('Search autocomplete dropdown', () => {
list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, groupIssuesPath, groupMRsPath);
});
+
it('should show Project specific dropdown menu', function() {
var list;
addBodyAttributes('project');
@@ -164,6 +167,7 @@ describe('Search autocomplete dropdown', () => {
list = widget.wrap.find('.dropdown-menu').find('ul');
return assertLinks(list, projectIssuesPath, projectMRsPath);
});
+
it('should show only Project mergeRequest dropdown menu items when project issues are disabled', function() {
addBodyAttributes('project');
disableProjectIssues();
@@ -172,6 +176,7 @@ describe('Search autocomplete dropdown', () => {
const list = widget.wrap.find('.dropdown-menu').find('ul');
assertLinks(list, null, projectMRsPath);
});
+
it('should not show category related menu if there is text in the input', function() {
var link, list;
addBodyAttributes('project');
@@ -182,6 +187,7 @@ describe('Search autocomplete dropdown', () => {
link = "a[href='" + projectIssuesPath + '/?assignee_id=' + userId + "']";
return expect(list.find(link).length).toBe(0);
});
+
it('should not submit the search form when selecting an autocomplete row with the keyboard', function() {
var ENTER = 13;
var DOWN = 40;
diff --git a/spec/javascripts/settings_panels_spec.js b/spec/javascripts/settings_panels_spec.js
index c1a69bd7018..3b681a9ff28 100644
--- a/spec/javascripts/settings_panels_spec.js
+++ b/spec/javascripts/settings_panels_spec.js
@@ -1,10 +1,11 @@
+import $ from 'jquery';
import initSettingsPanels from '~/settings_panels';
describe('Settings Panels', () => {
- preloadFixtures('projects/ci_cd_settings.html.raw');
+ preloadFixtures('groups/edit.html.raw');
beforeEach(() => {
- loadFixtures('projects/ci_cd_settings.html.raw');
+ loadFixtures('groups/edit.html.raw');
});
describe('initSettingsPane', () => {
@@ -13,17 +14,32 @@ describe('Settings Panels', () => {
});
it('should expand linked hash fragment panel', () => {
- window.location.hash = '#autodevops-settings';
+ window.location.hash = '#js-general-settings';
- const pipelineSettingsPanel = document.querySelector('#autodevops-settings');
+ const panel = document.querySelector('#js-general-settings');
// Our test environment automatically expands everything so we need to clear that out first
- pipelineSettingsPanel.classList.remove('expanded');
+ panel.classList.remove('expanded');
- expect(pipelineSettingsPanel.classList.contains('expanded')).toBe(false);
+ expect(panel.classList.contains('expanded')).toBe(false);
initSettingsPanels();
- expect(pipelineSettingsPanel.classList.contains('expanded')).toBe(true);
+ expect(panel.classList.contains('expanded')).toBe(true);
});
});
+
+ it('does not change the text content of triggers', () => {
+ const panel = document.querySelector('#js-general-settings');
+ const trigger = panel.querySelector('.js-settings-toggle-trigger-only');
+ const originalText = trigger.textContent;
+
+ initSettingsPanels();
+
+ expect(panel.classList.contains('expanded')).toBe(true);
+
+ $(trigger).click();
+
+ expect(panel.classList.contains('expanded')).toBe(false);
+ expect(trigger.textContent).toEqual(originalText);
+ });
});
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
index af3a5d58ba7..512be88c24c 100644
--- a/spec/javascripts/syntax_highlight_spec.js
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -25,6 +25,7 @@ describe('Syntax Highlighter', function() {
beforeEach(function() {
return setFixtures("<div class=\"parent\">\n <div class=\"js-syntax-highlight\"></div>\n <div class=\"foo\"></div>\n <div class=\"js-syntax-highlight\"></div>\n</div>");
});
+
it('applies highlighting to all applicable children', function() {
stubUserColorScheme('monokai');
syntaxHighlight($('.parent'));
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
index 91e81a0675a..305cee94f57 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_memory_usage_spec.js
@@ -137,7 +137,7 @@ describe('MemoryUsage', () => {
} = vm;
expect(hasMetrics).toBeTruthy();
- expect(memoryMetrics.length > 0).toBeTruthy();
+ expect(memoryMetrics.length).toBeGreaterThan(0);
expect(deploymentTime).toEqual(deployment_time);
expect(memoryFrom).toEqual('9.13');
expect(memoryTo).toEqual('4.28');
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
index efa5c878678..033cb694249 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_merged_spec.js
@@ -157,6 +157,16 @@ describe('MRWidgetMerged', () => {
expect(selectors.copyMergeShaButton.getAttribute('data-clipboard-text')).toBe(vm.mr.mergeCommitSha);
});
+ it('hides button to copy commit SHA if SHA does not exist', (done) => {
+ vm.mr.mergeCommitSha = null;
+
+ Vue.nextTick(() => {
+ expect(selectors.copyMergeShaButton).not.toExist();
+ expect(vm.$el.querySelector('.mr-info-list').innerText).not.toContain('with');
+ done();
+ });
+ });
+
it('shows merge commit SHA link', () => {
expect(selectors.mergeCommitShaLink).toExist();
expect(selectors.mergeCommitShaLink.text).toContain(vm.mr.shortMergeCommitSha);
diff --git a/spec/javascripts/vue_shared/components/memory_graph_spec.js b/spec/javascripts/vue_shared/components/memory_graph_spec.js
index 65d8ed39ade..0982b3e1f38 100644
--- a/spec/javascripts/vue_shared/components/memory_graph_spec.js
+++ b/spec/javascripts/vue_shared/components/memory_graph_spec.js
@@ -52,8 +52,8 @@ describe('MemoryGraph', () => {
it('should show human readable median value based on provided median timestamp', () => {
vm.deploymentTime = mockMedian;
const formattedMedian = vm.getFormattedMedian;
- expect(formattedMedian.indexOf('Deployed') > -1).toBeTruthy();
- expect(formattedMedian.indexOf('ago') > -1).toBeTruthy();
+ expect(formattedMedian.indexOf('Deployed')).toBeGreaterThan(-1);
+ expect(formattedMedian.indexOf('ago')).toBeGreaterThan(-1);
});
});
});
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index ed6fa3d229f..e2bee22cf1f 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::BitbucketImport::ProjectCreator do
owner: "asd",
full_name: 'Vim repo',
visibility_level: Gitlab::VisibilityLevel::PRIVATE,
- clone_url: 'ssh://git@bitbucket.org/asd/vim.git',
+ clone_url: 'http://bitbucket.org/asd/vim.git',
has_wiki?: false)
end
@@ -32,7 +32,7 @@ describe Gitlab::BitbucketImport::ProjectCreator do
project_creator = described_class.new(repo, 'vim', namespace, user, access_params)
project = project_creator.execute
- expect(project.import_url).to eq("ssh://git@bitbucket.org/asd/vim.git")
+ expect(project.import_url).to eq("http://bitbucket.org/asd/vim.git")
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
end
end
diff --git a/spec/lib/gitlab/blob_helper_spec.rb b/spec/lib/gitlab/blob_helper_spec.rb
new file mode 100644
index 00000000000..0b56f8687c3
--- /dev/null
+++ b/spec/lib/gitlab/blob_helper_spec.rb
@@ -0,0 +1,125 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BlobHelper do
+ include FakeBlobHelpers
+
+ let(:project) { create(:project) }
+ let(:blob) { fake_blob(path: 'file.txt') }
+ let(:large_blob) { fake_blob(path: 'test.pdf', size: 2.megabytes, binary: true) }
+
+ describe '#extname' do
+ it 'returns the extension' do
+ expect(blob.extname).to eq('.txt')
+ end
+ end
+
+ describe '#known_extension?' do
+ it 'returns true' do
+ expect(blob.known_extension?).to be_truthy
+ end
+ end
+
+ describe '#viewable' do
+ it 'returns true' do
+ expect(blob.viewable?).to be_truthy
+ end
+
+ it 'returns false' do
+ expect(large_blob.viewable?).to be_falsey
+ end
+ end
+
+ describe '#large?' do
+ it 'returns false' do
+ expect(blob.large?).to be_falsey
+ end
+
+ it 'returns true' do
+ expect(large_blob.large?).to be_truthy
+ end
+ end
+
+ describe '#binary?' do
+ it 'returns true' do
+ expect(large_blob.binary?).to be_truthy
+ end
+
+ it 'returns false' do
+ expect(blob.binary?).to be_falsey
+ end
+ end
+
+ describe '#text?' do
+ it 'returns true' do
+ expect(blob.text?).to be_truthy
+ end
+
+ it 'returns false' do
+ expect(large_blob.text?).to be_falsey
+ end
+ end
+
+ describe '#image?' do
+ it 'returns false' do
+ expect(blob.image?).to be_falsey
+ end
+ end
+
+ describe '#mime_type' do
+ it 'returns text/plain' do
+ expect(blob.mime_type).to eq('text/plain')
+ end
+
+ it 'returns application/pdf' do
+ expect(large_blob.mime_type).to eq('application/pdf')
+ end
+ end
+
+ describe '#binary_mime_type?' do
+ it 'returns false' do
+ expect(blob.binary_mime_type?).to be_falsey
+ end
+ end
+
+ describe '#lines' do
+ it 'returns the payload in an Array' do
+ expect(blob.lines).to eq(['foo'])
+ end
+ end
+
+ describe '#content_type' do
+ it 'returns text/plain' do
+ expect(blob.content_type).to eq('text/plain; charset=utf-8')
+ end
+
+ it 'returns text/plain' do
+ expect(large_blob.content_type).to eq('application/pdf')
+ end
+ end
+
+ describe '#encoded_newlines_re' do
+ it 'returns a regular expression' do
+ expect(blob.encoded_newlines_re).to eq(/\r\n|\r|\n/)
+ end
+ end
+
+ describe '#ruby_encoding' do
+ it 'returns UTF-8' do
+ expect(blob.ruby_encoding).to eq('UTF-8')
+ end
+ end
+
+ describe '#encoding' do
+ it 'returns UTF-8' do
+ expect(blob.ruby_encoding).to eq('UTF-8')
+ end
+ end
+
+ describe '#empty?' do
+ it 'returns false' do
+ expect(blob.empty?).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index 18658588a40..4d5081b0a75 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -49,8 +49,6 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
end
it 'only connects to redis twice' do
- # Stub circuitbreaker so it doesn't count the redis connections in there
- stub_circuit_breaker(project_without_status)
expect(Gitlab::Redis::Cache).to receive(:with).exactly(2).and_call_original
described_class.load_in_batch_for_projects([project_without_status])
@@ -302,13 +300,4 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
end
end
end
-
- def stub_circuit_breaker(project)
- fake_circuitbreaker = double
- allow(fake_circuitbreaker).to receive(:perform).and_yield
- allow(project.repository.raw_repository)
- .to receive(:circuit_breaker).and_return(fake_circuitbreaker)
- allow(project.repository)
- .to receive(:circuit_breaker).and_return(fake_circuitbreaker)
- end
end
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index 694d4ce160a..d97fdc01109 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
end
context 'when pipeline has a core status' do
- HasStatus::AVAILABLE_STATUSES.each do |simple_status|
+ (HasStatus::AVAILABLE_STATUSES - HasStatus::BLOCKED_STATUS).each do |simple_status|
context "when core status is #{simple_status}" do
let(:pipeline) { create(:ci_pipeline, status: simple_status) }
@@ -23,24 +23,12 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
expect(factory.core_status).to be_a expected_status
end
- if simple_status == 'manual'
- it 'matches a correct extended statuses' do
- expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Pipeline::Blocked]
- end
- elsif simple_status == 'scheduled'
- it 'matches a correct extended statuses' do
- expect(factory.extended_statuses)
- .to eq [Gitlab::Ci::Status::Pipeline::Scheduled]
- end
- else
- it 'does not match extended statuses' do
- expect(factory.extended_statuses).to be_empty
- end
-
- it "fabricates a core status #{simple_status}" do
- expect(status).to be_a expected_status
- end
+ it 'does not match extended statuses' do
+ expect(factory.extended_statuses).to be_empty
+ end
+
+ it "fabricates a core status #{simple_status}" do
+ expect(status).to be_a expected_status
end
it 'extends core status with common pipeline methods' do
@@ -51,6 +39,48 @@ describe Gitlab::Ci::Status::Pipeline::Factory do
end
end
end
+
+ context "when core status is manual" do
+ let(:pipeline) { create(:ci_pipeline, status: :manual) }
+
+ it "matches manual core status" do
+ expect(factory.core_status)
+ .to be_a Gitlab::Ci::Status::Manual
+ end
+
+ it 'matches a correct extended statuses' do
+ expect(factory.extended_statuses)
+ .to eq [Gitlab::Ci::Status::Pipeline::Blocked]
+ end
+
+ it 'extends core status with common pipeline methods' do
+ expect(status).to have_details
+ expect(status).not_to have_action
+ expect(status.details_path)
+ .to include "pipelines/#{pipeline.id}"
+ end
+ end
+
+ context "when core status is scheduled" do
+ let(:pipeline) { create(:ci_pipeline, status: :scheduled) }
+
+ it "matches scheduled core status" do
+ expect(factory.core_status)
+ .to be_a Gitlab::Ci::Status::Scheduled
+ end
+
+ it 'matches a correct extended statuses' do
+ expect(factory.extended_statuses)
+ .to eq [Gitlab::Ci::Status::Pipeline::Scheduled]
+ end
+
+ it 'extends core status with common pipeline methods' do
+ expect(status).to have_details
+ expect(status).not_to have_action
+ expect(status.details_path)
+ .to include "pipelines/#{pipeline.id}"
+ end
+ end
end
context 'when pipeline has warnings' do
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index 1bd077ddbdf..a955ce54e85 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -267,11 +267,6 @@ FILE
it 'includes the full content of the conflict' do
expect(conflict_file.as_json(full_content: true)).to have_key(:content)
end
-
- it 'includes the detected language of the conflict file' do
- expect(conflict_file.as_json(full_content: true)[:blob_ace_mode])
- .to eq('ruby')
- end
end
end
end
diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb
index 9e351368b22..294ec2c2fd6 100644
--- a/spec/lib/gitlab/file_detector_spec.rb
+++ b/spec/lib/gitlab/file_detector_spec.rb
@@ -46,10 +46,6 @@ describe Gitlab::FileDetector do
expect(described_class.type_of('.gitignore')).to eq(:gitignore)
end
- it 'returns the type of a Koding config file' do
- expect(described_class.type_of('.koding.yml')).to eq(:koding)
- end
-
it 'returns the type of a GitLab CI config file' do
expect(described_class.type_of('.gitlab-ci.yml')).to eq(:gitlab_ci)
end
diff --git a/spec/lib/gitlab/git/blob_snippet_spec.rb b/spec/lib/gitlab/git/blob_snippet_spec.rb
deleted file mode 100644
index 6effec8295c..00000000000
--- a/spec/lib/gitlab/git/blob_snippet_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# encoding: UTF-8
-
-require "spec_helper"
-
-describe Gitlab::Git::BlobSnippet, :seed_helper do
- describe '#data' do
- context 'empty lines' do
- let(:snippet) { Gitlab::Git::BlobSnippet.new('master', nil, nil, nil) }
-
- it { expect(snippet.data).to be_nil }
- end
-
- context 'present lines' do
- let(:snippet) { Gitlab::Git::BlobSnippet.new('master', %w(wow much), 1, 'wow.rb') }
-
- it { expect(snippet.data).to eq("wow\nmuch") }
- end
- end
-end
diff --git a/spec/lib/gitlab/git/storage/checker_spec.rb b/spec/lib/gitlab/git/storage/checker_spec.rb
deleted file mode 100644
index d74c3bcb04c..00000000000
--- a/spec/lib/gitlab/git/storage/checker_spec.rb
+++ /dev/null
@@ -1,132 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Storage::Checker, :clean_gitlab_redis_shared_state do
- let(:storage_name) { 'default' }
- let(:hostname) { Gitlab::Environment.hostname }
- let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" }
-
- subject(:checker) { described_class.new(storage_name) }
-
- def value_from_redis(name)
- Gitlab::Git::Storage.redis.with do |redis|
- redis.hmget(cache_key, name)
- end.first
- end
-
- def set_in_redis(name, value)
- Gitlab::Git::Storage.redis.with do |redis|
- redis.hmset(cache_key, name, value)
- end.first
- end
-
- describe '.check_all' do
- it 'calls a check for each storage' do
- fake_checker_default = double
- fake_checker_broken = double
- fake_logger = fake_logger
-
- expect(described_class).to receive(:new).with('default', fake_logger) { fake_checker_default }
- expect(described_class).to receive(:new).with('broken', fake_logger) { fake_checker_broken }
- expect(fake_checker_default).to receive(:check_with_lease)
- expect(fake_checker_broken).to receive(:check_with_lease)
-
- described_class.check_all(fake_logger)
- end
-
- context 'with broken storage', :broken_storage do
- it 'returns the results' do
- expected_result = [
- { storage: 'default', success: true },
- { storage: 'broken', success: false }
- ]
-
- expect(described_class.check_all).to eq(expected_result)
- end
- end
- end
-
- describe '#initialize' do
- it 'assigns the settings' do
- expect(checker.hostname).to eq(hostname)
- expect(checker.storage).to eq('default')
- expect(checker.storage_path).to eq(TestEnv.repos_path)
- end
- end
-
- describe '#check_with_lease' do
- it 'only allows one check at a time' do
- expect(checker).to receive(:check).once { sleep 1 }
-
- thread = Thread.new { checker.check_with_lease }
- checker.check_with_lease
- thread.join
- end
-
- it 'returns a result hash' do
- expect(checker.check_with_lease).to eq(storage: 'default', success: true)
- end
- end
-
- describe '#check' do
- it 'tracks that the storage was accessible' do
- set_in_redis(:failure_count, 10)
- set_in_redis(:last_failure, Time.now.to_f)
-
- checker.check
-
- expect(value_from_redis(:failure_count).to_i).to eq(0)
- expect(value_from_redis(:last_failure)).to be_empty
- expect(value_from_redis(:first_failure)).to be_empty
- end
-
- it 'calls the check with the correct arguments' do
- stub_application_setting(circuitbreaker_storage_timeout: 30,
- circuitbreaker_access_retries: 3)
-
- expect(Gitlab::Git::Storage::ForkedStorageCheck)
- .to receive(:storage_available?).with(TestEnv.repos_path, 30, 3)
- .and_call_original
-
- checker.check
- end
-
- it 'returns `true`' do
- expect(checker.check).to eq(true)
- end
-
- it 'maintains known storage keys' do
- Timecop.freeze do
- # Insert an old key to expire
- old_entry = Time.now.to_i - 3.days.to_i
- Gitlab::Git::Storage.redis.with do |redis|
- redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, old_entry, 'to_be_removed')
- end
-
- checker.check
-
- known_keys = Gitlab::Git::Storage.redis.with do |redis|
- redis.zrange(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, -1)
- end
-
- expect(known_keys).to contain_exactly(cache_key)
- end
- end
-
- context 'the storage is not available', :broken_storage do
- let(:storage_name) { 'broken' }
-
- it 'tracks that the storage was inaccessible' do
- Timecop.freeze do
- expect { checker.check }.to change { value_from_redis(:failure_count).to_i }.by(1)
-
- expect(value_from_redis(:last_failure)).not_to be_empty
- expect(value_from_redis(:first_failure)).not_to be_empty
- end
- end
-
- it 'returns `false`' do
- expect(checker.check).to eq(false)
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb b/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
deleted file mode 100644
index 210b90bfba9..00000000000
--- a/spec/lib/gitlab/git/storage/circuit_breaker_spec.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Storage::CircuitBreaker, :broken_storage do
- let(:storage_name) { 'default' }
- let(:circuit_breaker) { described_class.new(storage_name, hostname) }
- let(:hostname) { Gitlab::Environment.hostname }
- let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" }
-
- def set_in_redis(name, value)
- Gitlab::Git::Storage.redis.with do |redis|
- redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
- redis.hmset(cache_key, name, value)
- end.first
- end
-
- before do
- # Override test-settings for the circuitbreaker with something more realistic
- # for these specs.
- stub_storage_settings('default' => {
- 'path' => TestEnv.repos_path
- },
- 'broken' => {
- 'path' => 'tmp/tests/non-existent-repositories'
- },
- 'nopath' => { 'path' => nil }
- )
- end
-
- describe '.for_storage', :request_store do
- it 'only builds a single circuitbreaker per storage' do
- expect(described_class).to receive(:new).once.and_call_original
-
- breaker = described_class.for_storage('default')
-
- expect(breaker).to be_a(described_class)
- expect(described_class.for_storage('default')).to eq(breaker)
- end
-
- it 'returns a broken circuit breaker for an unknown storage' do
- expect(described_class.for_storage('unknown').circuit_broken?).to be_truthy
- end
-
- it 'returns a broken circuit breaker when the path is not set' do
- expect(described_class.for_storage('nopath').circuit_broken?).to be_truthy
- end
- end
-
- describe '#initialize' do
- it 'assigns the settings' do
- expect(circuit_breaker.hostname).to eq(hostname)
- expect(circuit_breaker.storage).to eq('default')
- end
- end
-
- context 'circuitbreaker settings' do
- before do
- stub_application_setting(circuitbreaker_failure_count_threshold: 0,
- circuitbreaker_failure_wait_time: 1,
- circuitbreaker_failure_reset_time: 2,
- circuitbreaker_storage_timeout: 3,
- circuitbreaker_access_retries: 4,
- circuitbreaker_backoff_threshold: 5)
- end
-
- describe '#failure_count_threshold' do
- it 'reads the value from settings' do
- expect(circuit_breaker.failure_count_threshold).to eq(0)
- end
- end
-
- describe '#check_interval' do
- it 'reads the value from settings' do
- expect(circuit_breaker.check_interval).to eq(1)
- end
- end
-
- describe '#failure_reset_time' do
- it 'reads the value from settings' do
- expect(circuit_breaker.failure_reset_time).to eq(2)
- end
- end
-
- describe '#storage_timeout' do
- it 'reads the value from settings' do
- expect(circuit_breaker.storage_timeout).to eq(3)
- end
- end
-
- describe '#access_retries' do
- it 'reads the value from settings' do
- expect(circuit_breaker.access_retries).to eq(4)
- end
- end
- end
-
- describe '#perform' do
- it 'raises the correct exception when the circuit is open' do
- set_in_redis(:last_failure, 1.day.ago.to_f)
- set_in_redis(:failure_count, 999)
-
- expect { |b| circuit_breaker.perform(&b) }
- .to raise_error do |exception|
- expect(exception).to be_kind_of(Gitlab::Git::Storage::CircuitOpen)
- expect(exception.retry_after).to eq(1800)
- end
- end
-
- it 'yields the block' do
- expect { |b| circuit_breaker.perform(&b) }
- .to yield_control
- end
-
- it 'checks if the storage is available' do
- expect(circuit_breaker).to receive(:check_storage_accessible!)
- .and_call_original
-
- circuit_breaker.perform { 'hello world' }
- end
-
- it 'returns the value of the block' do
- result = circuit_breaker.perform { 'return value' }
-
- expect(result).to eq('return value')
- end
-
- it 'raises possible errors' do
- expect { circuit_breaker.perform { raise Rugged::OSError.new('Broken') } }
- .to raise_error(Rugged::OSError)
- end
-
- context 'with the feature disabled' do
- before do
- stub_feature_flags(git_storage_circuit_breaker: false)
- end
-
- it 'returns the block without checking accessibility' do
- expect(circuit_breaker).not_to receive(:check_storage_accessible!)
-
- result = circuit_breaker.perform { 'hello' }
-
- expect(result).to eq('hello')
- end
-
- it 'allows enabling the feature using an ENV var' do
- stub_env('GIT_STORAGE_CIRCUIT_BREAKER', 'true')
- expect(circuit_breaker).to receive(:check_storage_accessible!)
-
- result = circuit_breaker.perform { 'hello' }
-
- expect(result).to eq('hello')
- end
- end
- end
-
- describe '#circuit_broken?' do
- it 'is working when there is no last failure' do
- set_in_redis(:last_failure, nil)
- set_in_redis(:failure_count, 0)
-
- expect(circuit_breaker.circuit_broken?).to be_falsey
- end
-
- it 'is broken when there are too many failures' do
- set_in_redis(:last_failure, 1.day.ago.to_f)
- set_in_redis(:failure_count, 200)
-
- expect(circuit_breaker.circuit_broken?).to be_truthy
- end
- end
-
- describe '#last_failure' do
- it 'returns the last failure time' do
- time = Time.parse("2017-05-26 17:52:30")
- set_in_redis(:last_failure, time.to_i)
-
- expect(circuit_breaker.last_failure).to eq(time)
- end
- end
-
- describe '#failure_count' do
- it 'returns the failure count' do
- set_in_redis(:failure_count, 7)
-
- expect(circuit_breaker.failure_count).to eq(7)
- end
- end
-end
diff --git a/spec/lib/gitlab/git/storage/failure_info_spec.rb b/spec/lib/gitlab/git/storage/failure_info_spec.rb
deleted file mode 100644
index bae88fdda86..00000000000
--- a/spec/lib/gitlab/git/storage/failure_info_spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Storage::FailureInfo, :broken_storage do
- let(:storage_name) { 'default' }
- let(:hostname) { Gitlab::Environment.hostname }
- let(:cache_key) { "storage_accessible:#{storage_name}:#{hostname}" }
-
- def value_from_redis(name)
- Gitlab::Git::Storage.redis.with do |redis|
- redis.hmget(cache_key, name)
- end.first
- end
-
- def set_in_redis(name, value)
- Gitlab::Git::Storage.redis.with do |redis|
- redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
- redis.hmset(cache_key, name, value)
- end.first
- end
-
- describe '.reset_all!' do
- it 'clears all entries form redis' do
- set_in_redis(:failure_count, 10)
-
- described_class.reset_all!
-
- key_exists = Gitlab::Git::Storage.redis.with { |redis| redis.exists(cache_key) }
-
- expect(key_exists).to be_falsey
- end
-
- it 'does not break when there are no keys in redis' do
- expect { described_class.reset_all! }.not_to raise_error
- end
- end
-
- describe '.load' do
- it 'loads failure information for a storage on a host' do
- first_failure = Time.parse("2017-11-14 17:52:30")
- last_failure = Time.parse("2017-11-14 18:54:37")
- failure_count = 11
-
- set_in_redis(:first_failure, first_failure.to_i)
- set_in_redis(:last_failure, last_failure.to_i)
- set_in_redis(:failure_count, failure_count.to_i)
-
- info = described_class.load(cache_key)
-
- expect(info.first_failure).to eq(first_failure)
- expect(info.last_failure).to eq(last_failure)
- expect(info.failure_count).to eq(failure_count)
- end
- end
-
- describe '#no_failures?' do
- it 'is true when there are no failures' do
- info = described_class.new(nil, nil, 0)
-
- expect(info.no_failures?).to be_truthy
- end
-
- it 'is false when there are failures' do
- info = described_class.new(Time.parse("2017-11-14 17:52:30"),
- Time.parse("2017-11-14 18:54:37"),
- 20)
-
- expect(info.no_failures?).to be_falsy
- end
- end
-end
diff --git a/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb b/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb
deleted file mode 100644
index 39a5d020bb4..00000000000
--- a/spec/lib/gitlab/git/storage/forked_storage_check_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Storage::ForkedStorageCheck, broken_storage: true, skip_database_cleaner: true do
- let(:existing_path) do
- existing_path = TestEnv.repos_path
- FileUtils.mkdir_p(existing_path)
- existing_path
- end
-
- describe '.storage_accessible?' do
- it 'detects when a storage is not available' do
- expect(described_class.storage_available?('/non/existant/path')).to be_falsey
- end
-
- it 'detects when a storage is available' do
- expect(described_class.storage_available?(existing_path)).to be_truthy
- end
-
- it 'returns false when the check takes to long' do
- # We're forking a process here that takes too long
- # It will be killed it's parent process will be killed by it's parent
- # and waited for inside `Gitlab::Git::Storage::ForkedStorageCheck.timeout_check`
- allow(described_class).to receive(:check_filesystem_in_process) do
- Process.spawn("sleep 10")
- end
- result = true
-
- runtime = Benchmark.realtime do
- result = described_class.storage_available?(existing_path, 0.5)
- end
-
- expect(result).to be_falsey
- expect(runtime).to be < 1.0
- end
-
- it 'will try the specified amount of times before failing' do
- allow(described_class).to receive(:check_filesystem_in_process) do
- Process.spawn("sleep 10")
- end
-
- expect(Process).to receive(:spawn).with('sleep 10').twice
- .and_call_original
-
- runtime = Benchmark.realtime do
- described_class.storage_available?(existing_path, 0.5, 2)
- end
-
- expect(runtime).to be < 1.0
- end
-
- describe 'when using paths with spaces' do
- let(:test_dir) { Rails.root.join('tmp', 'tests', 'storage_check') }
- let(:path_with_spaces) { File.join(test_dir, 'path with spaces') }
-
- around do |example|
- FileUtils.mkdir_p(path_with_spaces)
- example.run
- FileUtils.rm_r(test_dir)
- end
-
- it 'works for paths with spaces' do
- expect(described_class.storage_available?(path_with_spaces)).to be_truthy
- end
-
- it 'works for a realpath with spaces' do
- symlink_location = File.join(test_dir, 'a symlink')
- FileUtils.ln_s(path_with_spaces, symlink_location)
-
- expect(described_class.storage_available?(symlink_location)).to be_truthy
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/git/storage/health_spec.rb b/spec/lib/gitlab/git/storage/health_spec.rb
deleted file mode 100644
index bb670fc5d94..00000000000
--- a/spec/lib/gitlab/git/storage/health_spec.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Storage::Health, broken_storage: true do
- let(:host1_key) { 'storage_accessible:broken:web01' }
- let(:host2_key) { 'storage_accessible:default:kiq01' }
-
- def set_in_redis(cache_key, value)
- Gitlab::Git::Storage.redis.with do |redis|
- redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
- redis.hmset(cache_key, :failure_count, value)
- end.first
- end
-
- describe '.for_failing_storages' do
- it 'only includes health status for failures' do
- set_in_redis(host1_key, 10)
- set_in_redis(host2_key, 0)
-
- expect(described_class.for_failing_storages.map(&:storage_name))
- .to contain_exactly('broken')
- end
- end
-
- describe '.for_all_storages' do
- it 'loads health status for all configured storages' do
- healths = described_class.for_all_storages
-
- expect(healths.map(&:storage_name)).to contain_exactly('default', 'broken')
- end
- end
-
- describe '#failing_info' do
- it 'only contains storages that have failures' do
- health = described_class.new('broken', [{ name: host1_key, failure_count: 0 },
- { name: host2_key, failure_count: 3 }])
-
- expect(health.failing_info).to contain_exactly({ name: host2_key, failure_count: 3 })
- end
- end
-
- describe '#total_failures' do
- it 'sums up all the failures' do
- health = described_class.new('broken', [{ name: host1_key, failure_count: 2 },
- { name: host2_key, failure_count: 3 }])
-
- expect(health.total_failures).to eq(5)
- end
- end
-
- describe '#failing_on_hosts' do
- it 'collects only the failing hostnames' do
- health = described_class.new('broken', [{ name: host1_key, failure_count: 2 },
- { name: host2_key, failure_count: 0 }])
-
- expect(health.failing_on_hosts).to contain_exactly('web01')
- end
- end
-end
diff --git a/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb b/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb
deleted file mode 100644
index 93ad20011de..00000000000
--- a/spec/lib/gitlab/git/storage/null_circuit_breaker_spec.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Git::Storage::NullCircuitBreaker do
- let(:storage) { 'default' }
- let(:hostname) { 'localhost' }
- let(:error) { nil }
-
- subject(:breaker) { described_class.new(storage, hostname, error: error) }
-
- context 'with an error' do
- let(:error) { Gitlab::Git::Storage::Misconfiguration.new('error') }
-
- describe '#perform' do
- it { expect { breaker.perform { 'ok' } }.to raise_error(error) }
- end
-
- describe '#circuit_broken?' do
- it { expect(breaker.circuit_broken?).to be_truthy }
- end
-
- describe '#last_failure' do
- it { Timecop.freeze { expect(breaker.last_failure).to eq(Time.now) } }
- end
-
- describe '#failure_count' do
- it { expect(breaker.failure_count).to eq(breaker.failure_count_threshold) }
- end
-
- describe '#failure_info' do
- it { expect(breaker.failure_info.no_failures?).to be_falsy }
- end
- end
-
- context 'not broken' do
- describe '#perform' do
- it { expect(breaker.perform { 'ok' }).to eq('ok') }
- end
-
- describe '#circuit_broken?' do
- it { expect(breaker.circuit_broken?).to be_falsy }
- end
-
- describe '#last_failure' do
- it { expect(breaker.last_failure).to be_nil }
- end
-
- describe '#failure_count' do
- it { expect(breaker.failure_count).to eq(0) }
- end
-
- describe '#failure_info' do
- it { expect(breaker.failure_info.no_failures?).to be_truthy }
- end
- end
-
- describe '#failure_count_threshold' do
- before do
- stub_application_setting(circuitbreaker_failure_count_threshold: 1)
- end
-
- it { expect(breaker.failure_count_threshold).to eq(1) }
- end
-
- it 'implements the CircuitBreaker interface' do
- ours = described_class.public_instance_methods
- theirs = Gitlab::Git::Storage::CircuitBreaker.public_instance_methods
-
- expect(theirs - ours).to be_empty
- end
-end
diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb
new file mode 100644
index 00000000000..c6f09ca2112
--- /dev/null
+++ b/spec/lib/gitlab/gon_helper_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::GonHelper do
+ let(:helper) do
+ Class.new do
+ include Gitlab::GonHelper
+ end.new
+ end
+
+ describe '#push_frontend_feature_flag' do
+ it 'pushes a feature flag to the frontend' do
+ gon = instance_double('gon')
+
+ allow(helper)
+ .to receive(:gon)
+ .and_return(gon)
+
+ expect(Feature)
+ .to receive(:enabled?)
+ .with(:my_feature_flag, 10)
+ .and_return(true)
+
+ expect(gon)
+ .to receive(:push)
+ .with({ features: { 'myFeatureFlag' => true } }, true)
+
+ helper.push_frontend_feature_flag(:my_feature_flag, 10)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/hook_data/base_builder_spec.rb b/spec/lib/gitlab/hook_data/base_builder_spec.rb
index a921dd766c3..e3c5ee3b905 100644
--- a/spec/lib/gitlab/hook_data/base_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/base_builder_spec.rb
@@ -8,57 +8,94 @@ describe Gitlab::HookData::BaseBuilder do
end
end
- subject { subclass.new(nil) }
-
using RSpec::Parameterized::TableSyntax
- where do
- {
- 'relative image URL' => {
- input: '![an image](foo.png)',
- output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)"
- },
- 'HTTP URL' => {
- input: '![an image](http://example.com/foo.png)',
- output: '![an image](http://example.com/foo.png)'
- },
- 'HTTPS URL' => {
- input: '![an image](https://example.com/foo.png)',
- output: '![an image](https://example.com/foo.png)'
- },
- 'protocol-relative URL' => {
- input: '![an image](//example.com/foo.png)',
- output: '![an image](//example.com/foo.png)'
- },
- 'URL reference by title' => {
- input: "![foo]\n\n[foo]: foo.png",
- output: "![foo]\n\n[foo]: foo.png"
- },
- 'URL reference by label' => {
- input: "![][foo]\n\n[foo]: foo.png",
- output: "![][foo]\n\n[foo]: foo.png"
- },
- 'in Markdown inline code block' => {
- input: '`![an image](foo.png)`',
- output: "`![an image](#{Gitlab.config.gitlab.url}/foo.png)`"
- },
- 'in HTML tag on the same line' => {
- input: '<p>![an image](foo.png)</p>',
- output: "<p>![an image](#{Gitlab.config.gitlab.url}/foo.png)</p>"
- },
- 'in Markdown multi-line code block' => {
- input: "```\n![an image](foo.png)\n```",
- output: "```\n![an image](foo.png)\n```"
- },
- 'in HTML tag on different lines' => {
- input: "<p>\n![an image](foo.png)\n</p>",
- output: "<p>\n![an image](foo.png)\n</p>"
+ context 'with an upload prefix specified' do
+ let(:project_with_path) { double(full_path: 'baz/bar') }
+ let(:object_with_project) { double(project: project_with_path) }
+ subject { subclass.new(object_with_project) }
+
+ where do
+ {
+ 'relative image URL' => {
+ input: '![an image](foo.png)',
+ output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)"
+ },
+ 'absolute upload URL' => {
+ input: '![an image](/uploads/foo.png)',
+ output: "![an image](#{Gitlab.config.gitlab.url}/baz/bar/uploads/foo.png)"
+ },
+ 'absolute non-upload URL' => {
+ input: '![an image](/downloads/foo.png)',
+ output: "![an image](#{Gitlab.config.gitlab.url}/downloads/foo.png)"
+ }
}
- }
+ end
+
+ with_them do
+ it { expect(subject.absolute_image_urls(input)).to eq(output) }
+ end
end
- with_them do
- it { expect(subject.absolute_image_urls(input)).to eq(output) }
+ context 'without an upload prefix specified' do
+ subject { subclass.new(nil) }
+
+ where do
+ {
+ 'relative image URL' => {
+ input: '![an image](foo.png)',
+ output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)"
+ },
+ 'absolute upload URL' => {
+ input: '![an image](/uploads/foo.png)',
+ output: "![an image](#{Gitlab.config.gitlab.url}/uploads/foo.png)"
+ },
+ 'absolute non-upload URL' => {
+ input: '![an image](/downloads/foo.png)',
+ output: "![an image](#{Gitlab.config.gitlab.url}/downloads/foo.png)"
+ },
+ 'HTTP URL' => {
+ input: '![an image](http://example.com/foo.png)',
+ output: '![an image](http://example.com/foo.png)'
+ },
+ 'HTTPS URL' => {
+ input: '![an image](https://example.com/foo.png)',
+ output: '![an image](https://example.com/foo.png)'
+ },
+ 'protocol-relative URL' => {
+ input: '![an image](//example.com/foo.png)',
+ output: '![an image](//example.com/foo.png)'
+ },
+ 'URL reference by title' => {
+ input: "![foo]\n\n[foo]: foo.png",
+ output: "![foo]\n\n[foo]: foo.png"
+ },
+ 'URL reference by label' => {
+ input: "![][foo]\n\n[foo]: foo.png",
+ output: "![][foo]\n\n[foo]: foo.png"
+ },
+ 'in Markdown inline code block' => {
+ input: '`![an image](foo.png)`',
+ output: "`![an image](#{Gitlab.config.gitlab.url}/foo.png)`"
+ },
+ 'in HTML tag on the same line' => {
+ input: '<p>![an image](foo.png)</p>',
+ output: "<p>![an image](#{Gitlab.config.gitlab.url}/foo.png)</p>"
+ },
+ 'in Markdown multi-line code block' => {
+ input: "```\n![an image](foo.png)\n```",
+ output: "```\n![an image](foo.png)\n```"
+ },
+ 'in HTML tag on different lines' => {
+ input: "<p>\n![an image](foo.png)\n</p>",
+ output: "<p>\n![an image](foo.png)\n</p>"
+ }
+ }
+ end
+
+ with_them do
+ it { expect(subject.absolute_image_urls(input)).to eq(output) }
+ end
end
end
end
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index 60093474f8a..f066c0e3813 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -46,7 +46,10 @@ describe Gitlab::HookData::IssueBuilder do
let(:builder) { described_class.new(issue_with_description) }
it 'sets the image to use an absolute URL' do
- expect(data[:description]).to eq("test![Issue_Image](#{Settings.gitlab.url}/uploads/abc/Issue_Image.png)")
+ expected_path = "#{issue_with_description.project.path_with_namespace}/uploads/abc/Issue_Image.png"
+
+ expect(data[:description])
+ .to eq("test![Issue_Image](#{Settings.gitlab.url}/#{expected_path})")
end
end
end
diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
index dd586af6118..9ce697adbba 100644
--- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb
@@ -58,11 +58,14 @@ describe Gitlab::HookData::MergeRequestBuilder do
end
context 'when the MR has an image in the description' do
- let(:mr_with_description) { create(:merge_request, description: 'test![Issue_Image](/uploads/abc/Issue_Image.png)') }
+ let(:mr_with_description) { create(:merge_request, description: 'test![MR_Image](/uploads/abc/MR_Image.png)') }
let(:builder) { described_class.new(mr_with_description) }
it 'sets the image to use an absolute URL' do
- expect(data[:description]).to eq("test![Issue_Image](#{Settings.gitlab.url}/uploads/abc/Issue_Image.png)")
+ expected_path = "#{mr_with_description.project.path_with_namespace}/uploads/abc/MR_Image.png"
+
+ expect(data[:description])
+ .to eq("test![MR_Image](#{Settings.gitlab.url}/#{expected_path})")
end
end
end
diff --git a/spec/lib/gitlab/language_data_spec.rb b/spec/lib/gitlab/language_data_spec.rb
new file mode 100644
index 00000000000..b08150855fe
--- /dev/null
+++ b/spec/lib/gitlab/language_data_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::LanguageData do
+ describe '#extensions' do
+ before do
+ described_class.clear_extensions!
+ end
+
+ it 'loads the extensions once' do
+ expect(YAML).to receive(:load_file).once.and_call_original
+
+ 2.times do
+ expect(described_class.extensions).to be_a(Set)
+ expect(described_class.extensions.count).to be > 0
+ # Sanity check for known extensions
+ expect(described_class.extensions).to include(*%w(.rb .yml .json))
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/storage_check/cli_spec.rb b/spec/lib/gitlab/storage_check/cli_spec.rb
deleted file mode 100644
index 6db0925899c..00000000000
--- a/spec/lib/gitlab/storage_check/cli_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::StorageCheck::CLI do
- let(:options) { Gitlab::StorageCheck::Options.new('unix://tmp/socket.sock', nil, 1, false) }
- subject(:runner) { described_class.new(options) }
-
- describe '#update_settings' do
- it 'updates the interval when changed in a valid response and logs the change' do
- fake_response = double
- expect(fake_response).to receive(:valid?).and_return(true)
- expect(fake_response).to receive(:check_interval).and_return(42)
- expect(runner.logger).to receive(:info)
-
- runner.update_settings(fake_response)
-
- expect(options.interval).to eq(42)
- end
- end
-end
diff --git a/spec/lib/gitlab/storage_check/gitlab_caller_spec.rb b/spec/lib/gitlab/storage_check/gitlab_caller_spec.rb
deleted file mode 100644
index d869022fd31..00000000000
--- a/spec/lib/gitlab/storage_check/gitlab_caller_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::StorageCheck::GitlabCaller do
- let(:options) { Gitlab::StorageCheck::Options.new('unix://tmp/socket.sock', nil, nil, false) }
- subject(:gitlab_caller) { described_class.new(options) }
-
- describe '#call!' do
- context 'when a socket is given' do
- it 'calls a socket' do
- fake_connection = double
- expect(fake_connection).to receive(:post)
- expect(Excon).to receive(:new).with('unix://tmp/socket.sock', socket: "tmp/socket.sock") { fake_connection }
-
- gitlab_caller.call!
- end
- end
-
- context 'when a host is given' do
- let(:options) { Gitlab::StorageCheck::Options.new('http://localhost:8080', nil, nil, false) }
-
- it 'it calls a http response' do
- fake_connection = double
- expect(Excon).to receive(:new).with('http://localhost:8080', socket: nil) { fake_connection }
- expect(fake_connection).to receive(:post)
-
- gitlab_caller.call!
- end
- end
- end
-
- describe '#headers' do
- it 'Adds the JSON header' do
- headers = gitlab_caller.headers
-
- expect(headers['Content-Type']).to eq('application/json')
- end
-
- context 'when a token was provided' do
- let(:options) { Gitlab::StorageCheck::Options.new('unix://tmp/socket.sock', 'atoken', nil, false) }
-
- it 'adds it to the headers' do
- expect(gitlab_caller.headers['TOKEN']).to eq('atoken')
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/storage_check/option_parser_spec.rb b/spec/lib/gitlab/storage_check/option_parser_spec.rb
deleted file mode 100644
index cad4dfbefcf..00000000000
--- a/spec/lib/gitlab/storage_check/option_parser_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::StorageCheck::OptionParser do
- describe '.parse!' do
- it 'assigns all options' do
- args = %w(--target unix://tmp/hello/world.sock --token thetoken --interval 42)
-
- options = described_class.parse!(args)
-
- expect(options.token).to eq('thetoken')
- expect(options.interval).to eq(42)
- expect(options.target).to eq('unix://tmp/hello/world.sock')
- end
-
- it 'requires the interval to be a number' do
- args = %w(--target unix://tmp/hello/world.sock --interval fortytwo)
-
- expect { described_class.parse!(args) }.to raise_error(OptionParser::InvalidArgument)
- end
-
- it 'raises an error if the scheme is not included' do
- args = %w(--target tmp/hello/world.sock)
-
- expect { described_class.parse!(args) }.to raise_error(OptionParser::InvalidArgument)
- end
-
- it 'raises an error if both socket and host are missing' do
- expect { described_class.parse!([]) }.to raise_error(OptionParser::InvalidArgument)
- end
- end
-end
diff --git a/spec/lib/gitlab/storage_check/response_spec.rb b/spec/lib/gitlab/storage_check/response_spec.rb
deleted file mode 100644
index 0ff2963e443..00000000000
--- a/spec/lib/gitlab/storage_check/response_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::StorageCheck::Response do
- let(:fake_json) do
- {
- check_interval: 42,
- results: [
- { storage: 'working', success: true },
- { storage: 'skipped', success: nil },
- { storage: 'failing', success: false }
- ]
- }.to_json
- end
-
- let(:fake_http_response) do
- fake_response = instance_double("Excon::Response - Status check")
- allow(fake_response).to receive(:status).and_return(200)
- allow(fake_response).to receive(:body).and_return(fake_json)
- allow(fake_response).to receive(:headers).and_return('Content-Type' => 'application/json')
-
- fake_response
- end
- let(:response) { described_class.new(fake_http_response) }
-
- describe '#valid?' do
- it 'is valid for a success response with parseable JSON' do
- expect(response).to be_valid
- end
- end
-
- describe '#check_interval' do
- it 'returns the result from the JSON' do
- expect(response.check_interval).to eq(42)
- end
- end
-
- describe '#responsive_shards' do
- it 'contains the names of working shards' do
- expect(response.responsive_shards).to contain_exactly('working')
- end
- end
-
- describe '#skipped_shards' do
- it 'contains the names of skipped shards' do
- expect(response.skipped_shards).to contain_exactly('skipped')
- end
- end
-
- describe '#failing_shards' do
- it 'contains the name of failing shards' do
- expect(response.failing_shards).to contain_exactly('failing')
- end
- end
-end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index d669c42ab4a..69ee5ff4bcd 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -127,6 +127,13 @@ describe Gitlab::UsageData do
expect(count_data[:clusters_applications_prometheus]).to eq(1)
expect(count_data[:clusters_applications_runner]).to eq(1)
end
+
+ it 'works when queries time out' do
+ allow_any_instance_of(ActiveRecord::Relation)
+ .to receive(:count).and_raise(ActiveRecord::StatementInvalid.new(''))
+
+ expect { subject }.not_to raise_error
+ end
end
describe '#features_usage_data_ce' do
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index dcfc80daa57..87b91286168 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -141,19 +141,6 @@ describe ApplicationSetting do
end
end
- context 'circuitbreaker settings' do
- [:circuitbreaker_failure_count_threshold,
- :circuitbreaker_check_interval,
- :circuitbreaker_failure_reset_time,
- :circuitbreaker_storage_timeout].each do |field|
- it "Validates #{field} as number" do
- is_expected.to validate_numericality_of(field)
- .only_integer
- .is_greater_than_or_equal_to(0)
- end
- end
- end
-
context 'repository storages' do
before do
storages = {
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index c55953c8d22..1580ef36127 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -4,7 +4,7 @@ describe Clusters::Applications::Ingress do
let(:ingress) { create(:clusters_applications_ingress) }
include_examples 'cluster application core specs', :clusters_applications_ingress
- include_examples 'cluster application status specs', :cluster_application_ingress
+ include_examples 'cluster application status specs', :clusters_applications_ingress
before do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index f34b4ece8db..f9776acd4c8 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -4,7 +4,7 @@ describe Clusters::Applications::Prometheus do
include KubernetesHelpers
include_examples 'cluster application core specs', :clusters_applications_prometheus
- include_examples 'cluster application status specs', :cluster_application_prometheus
+ include_examples 'cluster application status specs', :clusters_applications_prometheus
describe '.installed' do
subject { described_class.installed }
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index eda8d519f60..42448b42c8e 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -4,7 +4,7 @@ describe Clusters::Applications::Runner do
let(:ci_runner) { create(:ci_runner) }
include_examples 'cluster application core specs', :clusters_applications_runner
- include_examples 'cluster application status specs', :cluster_application_runner
+ include_examples 'cluster application status specs', :clusters_applications_runner
it { is_expected.to belong_to(:runner) }
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 2727191eb9b..34d321ec604 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Clusters::Cluster do
@@ -15,8 +17,9 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:on_creation?).to(:provider) }
it { is_expected.to delegate_method(:active?).to(:platform_kubernetes).with_prefix }
it { is_expected.to delegate_method(:rbac?).to(:platform_kubernetes).with_prefix }
- it { is_expected.to delegate_method(:installed?).to(:application_helm).with_prefix }
- it { is_expected.to delegate_method(:installed?).to(:application_ingress).with_prefix }
+ it { is_expected.to delegate_method(:available?).to(:application_helm).with_prefix }
+ it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix }
+ it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
it { is_expected.to respond_to :project }
describe '.enabled' do
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index b335e0fbeb3..182070781dd 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -39,6 +39,29 @@ describe Deployment do
end
end
+ describe 'scopes' do
+ describe 'last_for_environment' do
+ let(:production) { create(:environment) }
+ let(:staging) { create(:environment) }
+ let(:testing) { create(:environment) }
+
+ let!(:deployments) do
+ [
+ create(:deployment, environment: production),
+ create(:deployment, environment: staging),
+ create(:deployment, environment: production)
+ ]
+ end
+
+ it 'retrieves last deployments for environments' do
+ last_deployments = described_class.last_for_environment([staging, production, testing])
+
+ expect(last_deployments.size).to eq(2)
+ expect(last_deployments).to eq(deployments.last(2))
+ end
+ end
+ end
+
describe '#includes_commit?' do
let(:project) { create(:project, :repository) }
let(:environment) { create(:environment, project: project) }
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index 7afb1b4a8e3..f2cb927df37 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe PrometheusService, :use_clean_rails_memory_store_caching do
@@ -83,13 +85,22 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end
end
- describe '#prometheus_installed?' do
+ describe '#prometheus_available?' do
context 'clusters with installed prometheus' do
let!(:cluster) { create(:cluster, projects: [project]) }
let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
it 'returns true' do
- expect(service.prometheus_installed?).to be(true)
+ expect(service.prometheus_available?).to be(true)
+ end
+ end
+
+ context 'clusters with updated prometheus' do
+ let!(:cluster) { create(:cluster, projects: [project]) }
+ let!(:prometheus) { create(:clusters_applications_prometheus, :updated, cluster: cluster) }
+
+ it 'returns true' do
+ expect(service.prometheus_available?).to be(true)
end
end
@@ -98,7 +109,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
it 'returns false' do
- expect(service.prometheus_installed?).to be(false)
+ expect(service.prometheus_available?).to be(false)
end
end
@@ -106,13 +117,13 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
let(:cluster) { create(:cluster, projects: [project]) }
it 'returns false' do
- expect(service.prometheus_installed?).to be(false)
+ expect(service.prometheus_available?).to be(false)
end
end
context 'no clusters' do
it 'returns false' do
- expect(service.prometheus_installed?).to be(false)
+ expect(service.prometheus_available?).to be(false)
end
end
end
@@ -150,7 +161,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
context 'with prometheus installed in the cluster' do
before do
- allow(service).to receive(:prometheus_installed?).and_return(true)
+ allow(service).to receive(:prometheus_available?).and_return(true)
end
context 'when service is inactive' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 3fecddefff2..a807c336165 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -229,54 +229,75 @@ describe Project do
end
it 'does not allow an invalid URI as import_url' do
- project2 = build(:project, import_url: 'invalid://')
+ project = build(:project, import_url: 'invalid://')
- expect(project2).not_to be_valid
+ expect(project).not_to be_valid
+ end
+
+ it 'does allow a SSH URI as import_url for persisted projects' do
+ project = create(:project)
+ project.import_url = 'ssh://test@gitlab.com/project.git'
+
+ expect(project).to be_valid
+ end
+
+ it 'does not allow a SSH URI as import_url for new projects' do
+ project = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
+
+ expect(project).not_to be_valid
end
it 'does allow a valid URI as import_url' do
- project2 = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
+ project = build(:project, import_url: 'http://gitlab.com/project.git')
- expect(project2).to be_valid
+ expect(project).to be_valid
end
it 'allows an empty URI' do
- project2 = build(:project, import_url: '')
+ project = build(:project, import_url: '')
- expect(project2).to be_valid
+ expect(project).to be_valid
end
it 'does not produce import data on an empty URI' do
- project2 = build(:project, import_url: '')
+ project = build(:project, import_url: '')
- expect(project2.import_data).to be_nil
+ expect(project.import_data).to be_nil
end
it 'does not produce import data on an invalid URI' do
- project2 = build(:project, import_url: 'test://')
+ project = build(:project, import_url: 'test://')
- expect(project2.import_data).to be_nil
+ expect(project.import_data).to be_nil
end
it "does not allow import_url pointing to localhost" do
- project2 = build(:project, import_url: 'http://localhost:9000/t.git')
+ project = build(:project, import_url: 'http://localhost:9000/t.git')
+
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Requests to localhost are not allowed')
+ end
+
+ it "does not allow import_url with invalid ports for new projects" do
+ project = build(:project, import_url: 'http://github.com:25/t.git')
- expect(project2).to be_invalid
- expect(project2.errors[:import_url].first).to include('Requests to localhost are not allowed')
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443')
end
- it "does not allow import_url with invalid ports" do
- project2 = build(:project, import_url: 'http://github.com:25/t.git')
+ it "does not allow import_url with invalid ports for persisted projects" do
+ project = create(:project)
+ project.import_url = 'http://github.com:25/t.git'
- expect(project2).to be_invalid
- expect(project2.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
end
it "does not allow import_url with invalid user" do
- project2 = build(:project, import_url: 'http://$user:password@github.com/t.git')
+ project = build(:project, import_url: 'http://$user:password@github.com/t.git')
- expect(project2).to be_invalid
- expect(project2.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
+ expect(project).to be_invalid
+ expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
end
describe 'project pending deletion' do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 77e549d9528..fb16a321e4b 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -30,7 +30,7 @@ describe Repository do
def expect_to_raise_storage_error
expect { yield }.to raise_error do |exception|
- storage_exceptions = [Gitlab::Git::Storage::Inaccessible, Gitlab::Git::CommandError, GRPC::Unavailable]
+ storage_exceptions = [Gitlab::Git::CommandError, GRPC::Unavailable]
known_exception = storage_exceptions.select { |e| exception.is_a?(e) }
expect(known_exception).not_to be_nil
@@ -1567,7 +1567,6 @@ describe Repository do
:license_blob,
:license_key,
:gitignore,
- :koding_yml,
:gitlab_ci_yml,
:branch_names,
:tag_names,
@@ -1921,19 +1920,6 @@ describe Repository do
end
end
- describe '#koding_yml', :use_clean_rails_memory_store_caching do
- it 'returns and caches the output' do
- expect(repository).to receive(:file_on_head)
- .with(:koding)
- .and_return(Gitlab::Git::Tree.new(path: '.koding.yml'))
- .once
-
- 2.times do
- expect(repository.koding_yml).to be_an_instance_of(Gitlab::Git::Tree)
- end
- end
- end
-
describe '#readme', :use_clean_rails_memory_store_caching do
context 'with a non-existing repository' do
it 'returns nil' do
@@ -2386,4 +2372,15 @@ describe Repository do
end
end
end
+
+ describe '#merge_base' do
+ set(:project) { create(:project, :repository) }
+ subject(:repository) { project.repository }
+
+ it 'only makes one gitaly call' do
+ expect(Gitlab::GitalyClient).to receive(:call).once.and_call_original
+
+ repository.merge_base('master', 'fix')
+ end
+ end
end
diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb
index 96193784072..3eb2f149311 100644
--- a/spec/presenters/project_presenter_spec.rb
+++ b/spec/presenters/project_presenter_spec.rb
@@ -410,36 +410,5 @@ describe ProjectPresenter do
end
end
end
-
- describe '#koding_anchor_data' do
- it 'returns link to set up Koding if user can push and no koding YML exists' do
- project.add_developer(user)
- allow(project.repository).to receive(:koding_yml).and_return(nil)
- allow(Gitlab::CurrentSettings).to receive(:koding_enabled?).and_return(true)
-
- expect(presenter.koding_anchor_data).to have_attributes(enabled: false,
- label: 'Set up Koding',
- link: presenter.add_koding_stack_path)
- end
-
- it 'returns nil if user cannot push' do
- expect(presenter.koding_anchor_data).to be_nil
- end
-
- it 'returns nil if koding is not enabled' do
- project.add_developer(user)
- allow(Gitlab::CurrentSettings).to receive(:koding_enabled?).and_return(false)
-
- expect(presenter.koding_anchor_data).to be_nil
- end
-
- it 'returns nil if koding YML already exists' do
- project.add_developer(user)
- allow(project.repository).to receive(:koding_yml).and_return(double)
- allow(Gitlab::CurrentSettings).to receive(:koding_enabled?).and_return(true)
-
- expect(presenter.koding_anchor_data).to be_nil
- end
- end
end
end
diff --git a/spec/requests/api/circuit_breakers_spec.rb b/spec/requests/api/circuit_breakers_spec.rb
index fe76f057115..6c7cb151c74 100644
--- a/spec/requests/api/circuit_breakers_spec.rb
+++ b/spec/requests/api/circuit_breakers_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe API::CircuitBreakers do
- let(:user) { create(:user) }
- let(:admin) { create(:admin) }
+ set(:user) { create(:user) }
+ set(:admin) { create(:admin) }
describe 'GET circuit_breakers/repository_storage' do
it 'returns a 401 for anonymous users' do
@@ -18,37 +18,26 @@ describe API::CircuitBreakers do
end
it 'returns an Array of storages' do
- expect(Gitlab::Git::Storage::Health).to receive(:for_all_storages) do
- [Gitlab::Git::Storage::Health.new('broken', [{ name: 'prefix:broken:web01', failure_count: 4 }])]
- end
-
get api('/circuit_breakers/repository_storage', admin)
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_kind_of(Array)
- expect(json_response.first['storage_name']).to eq('broken')
- expect(json_response.first['failing_on_hosts']).to eq(['web01'])
- expect(json_response.first['total_failures']).to eq(4)
+ expect(json_response).to be_empty
end
describe 'GET circuit_breakers/repository_storage/failing' do
it 'returns an array of failing storages' do
- expect(Gitlab::Git::Storage::Health).to receive(:for_failing_storages) do
- [Gitlab::Git::Storage::Health.new('broken', [{ name: 'prefix:broken:web01', failure_count: 4 }])]
- end
-
get api('/circuit_breakers/repository_storage/failing', admin)
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_kind_of(Array)
+ expect(json_response).to be_empty
end
end
end
describe 'DELETE circuit_breakers/repository_storage' do
it 'clears all circuit_breakers' do
- expect(Gitlab::Git::Storage::FailureInfo).to receive(:reset_all!)
-
delete api('/circuit_breakers/repository_storage', admin)
expect(response).to have_gitlab_http_status(204)
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 519638ebb82..fa38751fe58 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -468,7 +468,7 @@ describe API::Repositories do
describe 'GET :id/repository/merge_base' do
let(:refs) do
- %w(304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209)
+ %w(304d257dcb821665ab5110318fc58a007bd104ed 0031876facac3f2b2702a0e53a26e89939a42209 570e7b2abdd848b95f2f578043fc23bd6f6fd24d)
end
subject(:request) do
@@ -534,7 +534,7 @@ describe API::Repositories do
request
expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['message']).to eq('Provide exactly 2 refs')
+ expect(json_response['message']).to eq('Provide at least 2 refs')
end
end
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index e379bd9785a..84c7210f6bb 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -2,18 +2,17 @@ require 'spec_helper'
describe API::Settings, 'Settings' do
let(:user) { create(:user) }
- let(:admin) { create(:admin) }
+ set(:admin) { create(:admin) }
describe "GET /application/settings" do
it "returns application settings" do
get api("/application/settings", admin)
+
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Hash
expect(json_response['default_projects_limit']).to eq(42)
expect(json_response['password_authentication_enabled_for_web']).to be_truthy
expect(json_response['repository_storages']).to eq(['default'])
- expect(json_response['koding_enabled']).to be_falsey
- expect(json_response['koding_url']).to be_nil
expect(json_response['plantuml_enabled']).to be_falsey
expect(json_response['plantuml_url']).to be_nil
expect(json_response['default_project_visibility']).to be_a String
@@ -23,7 +22,6 @@ describe API::Settings, 'Settings' do
expect(json_response['dsa_key_restriction']).to eq(0)
expect(json_response['ecdsa_key_restriction']).to eq(0)
expect(json_response['ed25519_key_restriction']).to eq(0)
- expect(json_response['circuitbreaker_failure_count_threshold']).not_to be_nil
expect(json_response['performance_bar_allowed_group_id']).to be_nil
expect(json_response['instance_statistics_visibility_private']).to be(false)
expect(json_response).not_to have_key('performance_bar_allowed_group_path')
@@ -47,8 +45,6 @@ describe API::Settings, 'Settings' do
default_projects_limit: 3,
password_authentication_enabled_for_web: false,
repository_storages: ['custom'],
- koding_enabled: true,
- koding_url: 'http://koding.example.com',
plantuml_enabled: true,
plantuml_url: 'http://plantuml.example.com',
default_snippet_visibility: 'internal',
@@ -62,7 +58,6 @@ describe API::Settings, 'Settings' do
dsa_key_restriction: 2048,
ecdsa_key_restriction: 384,
ed25519_key_restriction: 256,
- circuitbreaker_check_interval: 2,
enforce_terms: true,
terms: 'Hello world!',
performance_bar_allowed_group_path: group.full_path,
@@ -73,8 +68,6 @@ describe API::Settings, 'Settings' do
expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['password_authentication_enabled_for_web']).to be_falsey
expect(json_response['repository_storages']).to eq(['custom'])
- expect(json_response['koding_enabled']).to be_truthy
- expect(json_response['koding_url']).to eq('http://koding.example.com')
expect(json_response['plantuml_enabled']).to be_truthy
expect(json_response['plantuml_url']).to eq('http://plantuml.example.com')
expect(json_response['default_snippet_visibility']).to eq('internal')
@@ -88,7 +81,6 @@ describe API::Settings, 'Settings' do
expect(json_response['dsa_key_restriction']).to eq(2048)
expect(json_response['ecdsa_key_restriction']).to eq(384)
expect(json_response['ed25519_key_restriction']).to eq(256)
- expect(json_response['circuitbreaker_check_interval']).to eq(2)
expect(json_response['enforce_terms']).to be(true)
expect(json_response['terms']).to eq('Hello world!')
expect(json_response['performance_bar_allowed_group_id']).to eq(group.id)
@@ -114,15 +106,6 @@ describe API::Settings, 'Settings' do
expect(json_response['performance_bar_allowed_group_id']).to be_nil
end
- context "missing koding_url value when koding_enabled is true" do
- it "returns a blank parameter error message" do
- put api("/application/settings", admin), koding_enabled: true
-
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['error']).to eq('koding_url is missing')
- end
- end
-
context "missing plantuml_url value when plantuml_enabled is true" do
it "returns a blank parameter error message" do
put api("/application/settings", admin), plantuml_enabled: true
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index dd8f6239587..a170bb14144 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -140,13 +140,6 @@ describe HelpController, "routing" do
end
end
-# koding GET /koding(.:format) koding#index
-describe KodingController, "routing" do
- it "to #index" do
- expect(get("/koding")).to route_to('koding#index')
- end
-end
-
# profile_account GET /profile/account(.:format) profile#account
# profile_history GET /profile/history(.:format) profile#history
# profile_password PUT /profile/password(.:format) profile#password_update
diff --git a/spec/rubocop/cop/qa/element_with_pattern_spec.rb b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
new file mode 100644
index 00000000000..c5beb40f9fd
--- /dev/null
+++ b/spec/rubocop/cop/qa/element_with_pattern_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/qa/element_with_pattern'
+
+describe RuboCop::Cop::QA::ElementWithPattern do
+ include CopHelper
+
+ let(:source_file) { 'qa/page.rb' }
+
+ subject(:cop) { described_class.new }
+
+ context 'in a QA file' do
+ before do
+ allow(cop).to receive(:in_qa_file?).and_return(true)
+ end
+
+ it "registers an offense for elements with a pattern" do
+ expect_offense(<<-RUBY)
+ view 'app/views/shared/groups/_search_form.html.haml' do
+ element :groups_filter, 'search_field_tag :filter'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter` instead.
+ element :groups_filter_placeholder, /Search by name/
+ ^^^^^^^^^^^^^^^^ Don't use a pattern for element, create a corresponding `qa-groups-filter-placeholder` instead.
+ end
+ RUBY
+ end
+
+ it "does not register an offense for element without a pattern" do
+ expect_no_offenses(<<-RUBY)
+ view 'app/views/shared/groups/_search_form.html.haml' do
+ element :groups_filter
+ element :groups_filter_placeholder
+ end
+ RUBY
+ end
+ end
+
+ context 'outside of a migration spec file' do
+ it "does not register an offense" do
+ expect_no_offenses(<<-RUBY)
+ describe 'foo' do
+ let(:user) { create(:user) }
+ end
+ RUBY
+ end
+ end
+end
diff --git a/spec/rubocop/qa_helpers_spec.rb b/spec/rubocop/qa_helpers_spec.rb
new file mode 100644
index 00000000000..26e4c1ca6f0
--- /dev/null
+++ b/spec/rubocop/qa_helpers_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require_relative '../../rubocop/qa_helpers'
+
+describe RuboCop::QAHelpers do
+ def parse_source(source, path = 'foo.rb')
+ buffer = Parser::Source::Buffer.new(path)
+ buffer.source = source
+
+ builder = RuboCop::AST::Builder.new
+ parser = Parser::CurrentRuby.new(builder)
+
+ parser.parse(buffer)
+ end
+
+ let(:cop) do
+ Class.new do
+ include RuboCop::QAHelpers
+ end.new
+ end
+
+ describe '#in_qa_file?' do
+ it 'returns true for a node in the qa/ directory' do
+ node = parse_source('10', Rails.root.join('qa', 'qa', 'page', 'dashboard', 'groups.rb'))
+
+ expect(cop.in_qa_file?(node)).to eq(true)
+ end
+
+ it 'returns false for a node outside the qa/ directory' do
+ node = parse_source('10', Rails.root.join('app', 'foo', 'foo.rb'))
+
+ expect(cop.in_qa_file?(node)).to eq(false)
+ end
+ end
+end
diff --git a/spec/serializers/deployment_serializer_spec.rb b/spec/serializers/deployment_serializer_spec.rb
new file mode 100644
index 00000000000..4834f5ede3c
--- /dev/null
+++ b/spec/serializers/deployment_serializer_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DeploymentSerializer do
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user, email: project.commit.author_email) }
+
+ let(:resource) { create(:deployment, project: project, sha: project.commit.id) }
+ let(:serializer) { described_class.new(request) }
+
+ shared_examples 'json schema' do
+ let(:json_entity) { subject.as_json }
+
+ it 'matches deployment entity schema' do
+ expect(json_entity).to match_schema('deployment')
+ end
+ end
+
+ describe '#represent' do
+ subject { serializer.represent(resource) }
+
+ let(:request) { { project: project, current_user: user } }
+
+ it_behaves_like 'json schema'
+ end
+
+ describe '#represent_concise' do
+ subject { serializer.represent_concise(resource) }
+
+ let(:request) { { project: project } }
+
+ it_behaves_like 'json schema'
+ end
+end
diff --git a/spec/services/applications/create_service_spec.rb b/spec/services/applications/create_service_spec.rb
index 9c43b56744b..c8134087fa1 100644
--- a/spec/services/applications/create_service_spec.rb
+++ b/spec/services/applications/create_service_spec.rb
@@ -1,17 +1,14 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe ::Applications::CreateService do
+ include TestRequestHelpers
+
let(:user) { create(:user) }
let(:params) { attributes_for(:application) }
- let(:request) do
- if Gitlab.rails5?
- ActionController::TestRequest.new({ remote_ip: "127.0.0.1" }, ActionController::TestSession.new)
- else
- ActionController::TestRequest.new(remote_ip: "127.0.0.1")
- end
- end
subject { described_class.new(user, params) }
- it { expect { subject.execute(request) }.to change { Doorkeeper::Application.count }.by(1) }
+ it { expect { subject.execute(test_request) }.to change { Doorkeeper::Application.count }.by(1) }
end
diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
new file mode 100644
index 00000000000..056db0c5486
--- /dev/null
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Applications::CreateService do
+ include TestRequestHelpers
+
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:user) { create(:user) }
+ let(:params) { { application: 'helm' } }
+ let(:service) { described_class.new(cluster, user, params) }
+
+ describe '#execute' do
+ before do
+ allow(ClusterInstallAppWorker).to receive(:perform_async)
+ end
+
+ subject { service.execute(test_request) }
+
+ it 'creates an application' do
+ expect do
+ subject
+
+ cluster.reload
+ end.to change(cluster, :application_helm)
+ end
+
+ it 'schedules an install via worker' do
+ expect(ClusterInstallAppWorker).to receive(:perform_async).with('helm', anything).once
+
+ subject
+ end
+
+ context 'jupyter application' do
+ let(:params) do
+ {
+ application: 'jupyter',
+ hostname: 'example.com'
+ }
+ end
+
+ before do
+ allow_any_instance_of(Clusters::Applications::ScheduleInstallationService).to receive(:execute)
+ end
+
+ it 'creates the application' do
+ expect do
+ subject
+
+ cluster.reload
+ end.to change(cluster, :application_jupyter)
+ end
+
+ it 'sets the hostname' do
+ expect(subject.hostname).to eq('example.com')
+ end
+
+ it 'sets the oauth_application' do
+ expect(subject.oauth_application).to be_present
+ end
+ end
+
+ context 'invalid application' do
+ let(:params) { { application: 'non-existent' } }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Clusters::Applications::CreateService::InvalidApplicationError)
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/applications/schedule_installation_service_spec.rb b/spec/services/clusters/applications/schedule_installation_service_spec.rb
index bca1e71bef2..21797edd533 100644
--- a/spec/services/clusters/applications/schedule_installation_service_spec.rb
+++ b/spec/services/clusters/applications/schedule_installation_service_spec.rb
@@ -10,14 +10,13 @@ describe Clusters::Applications::ScheduleInstallationService do
expect(ClusterInstallAppWorker).not_to receive(:perform_async)
count_before = count_scheduled
- expect { service.execute(application) }.to raise_error(StandardError)
+ expect { service.execute }.to raise_error(StandardError)
expect(count_scheduled).to eq(count_before)
end
end
describe '#execute' do
- let(:project) { double(:project) }
- let(:service) { described_class.new(project, nil) }
+ let(:service) { described_class.new(application) }
context 'when application is installable' do
let(:application) { create(:clusters_applications_helm, :installable) }
@@ -25,7 +24,7 @@ describe Clusters::Applications::ScheduleInstallationService do
it 'make the application scheduled' do
expect(ClusterInstallAppWorker).to receive(:perform_async).with(application.name, kind_of(Numeric)).once
- expect { service.execute(application) }.to change { application.class.with_status(:scheduled).count }.by(1)
+ expect { service.execute }.to change { application.class.with_status(:scheduled).count }.by(1)
end
end
diff --git a/spec/services/clusters/create_service_spec.rb b/spec/services/clusters/create_service_spec.rb
index 1685dc748bd..3959295c13e 100644
--- a/spec/services/clusters/create_service_spec.rb
+++ b/spec/services/clusters/create_service_spec.rb
@@ -5,7 +5,7 @@ describe Clusters::CreateService do
let(:project) { create(:project) }
let(:user) { create(:user) }
- subject { described_class.new(project, user, params).execute(access_token) }
+ subject { described_class.new(user, params).execute(project: project, access_token: access_token) }
context 'when provider is gcp' do
context 'when project has no clusters' do
diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb
index 2d91a21035d..dcd75b6912d 100644
--- a/spec/services/clusters/update_service_spec.rb
+++ b/spec/services/clusters/update_service_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Clusters::UpdateService do
describe '#execute' do
- subject { described_class.new(cluster.project, cluster.user, params).execute(cluster) }
+ subject { described_class.new(cluster.user, params).execute(cluster) }
let(:cluster) { create(:cluster, :project, :provided_by_user) }
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index beff499f2be..1d31d26f418 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -65,10 +65,12 @@ describe Projects::DestroyService do
context 'Sidekiq inline' do
before do
- # Run sidekiq immediatly to check that renamed repository will be removed
+ # Run sidekiq immediately to check that renamed repository will be removed
perform_enqueued_jobs { destroy_project(project, user, {}) }
end
+ it_behaves_like 'deleting the project'
+
context 'when has remote mirrors' do
let!(:project) do
create(:project, :repository, namespace: user.namespace).tap do |project|
@@ -82,13 +84,28 @@ describe Projects::DestroyService do
end
end
- it_behaves_like 'deleting the project'
-
it 'invalidates personal_project_count cache' do
expect(user).to receive(:invalidate_personal_projects_count)
destroy_project(project, user)
end
+
+ context 'when project has exports' do
+ let!(:project_with_export) do
+ create(:project, :repository, namespace: user.namespace).tap do |project|
+ create(:import_export_upload,
+ project: project,
+ export_file: fixture_file_upload('spec/fixtures/project_export.tar.gz'))
+ end
+ end
+ let!(:async) { true }
+
+ it 'destroys project and export' do
+ expect { destroy_project(project_with_export, user) }.to change(ImportExportUpload, :count).by(-1)
+
+ expect(Project.all).not_to include(project_with_export)
+ end
+ end
end
context 'Sidekiq fake' do
diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb
index e2a600d12d1..e6ffa2b957b 100644
--- a/spec/services/projects/import_service_spec.rb
+++ b/spec/services/projects/import_service_spec.rb
@@ -235,7 +235,7 @@ describe Projects::ImportService do
result = described_class.new(project, user).execute
expect(result[:status]).to eq :error
- expect(result[:message]).to include('Only allowed ports are 22, 80, 443')
+ expect(result[:message]).to include('Only allowed ports are 80, 443')
end
end
diff --git a/spec/support/features/issuable_quick_actions_shared_examples.rb b/spec/support/features/issuable_quick_actions_shared_examples.rb
index 846e697eb96..2a883ce1074 100644
--- a/spec/support/features/issuable_quick_actions_shared_examples.rb
+++ b/spec/support/features/issuable_quick_actions_shared_examples.rb
@@ -77,6 +77,15 @@ shared_examples 'issuable record that supports quick actions in its description
expect(issuable.labels).to eq [label_bug]
expect(issuable.milestone).to eq milestone
end
+
+ it 'removes the quick action from note and explains it in the preview' do
+ preview_note("Awesome!\n\n/close")
+
+ expect(page).to have_content 'Awesome!'
+ expect(page).not_to have_content '/close'
+ issuable_name = issuable.is_a?(Issue) ? 'issue' : 'merge request'
+ expect(page).to have_content "Closes this #{issuable_name}."
+ end
end
context 'with a note containing only commands' do
diff --git a/spec/support/helpers/test_request_helpers.rb b/spec/support/helpers/test_request_helpers.rb
new file mode 100644
index 00000000000..187a0e07891
--- /dev/null
+++ b/spec/support/helpers/test_request_helpers.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module TestRequestHelpers
+ def test_request(remote_ip: '127.0.0.1')
+ if Gitlab.rails5?
+ ActionController::TestRequest.new({ remote_ip: remote_ip }, ActionController::TestSession.new)
+ else
+ ActionController::TestRequest.new(remote_ip: remote_ip)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
new file mode 100644
index 00000000000..ba363593120
--- /dev/null
+++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
@@ -0,0 +1,24 @@
+shared_examples 'dirty submit form' do |selector_args|
+ selectors = selector_args.is_a?(Array) ? selector_args : [selector_args]
+
+ selectors.each do |selector|
+ it "disables #{selector[:form]} submit until there are changes", :js do
+ form = find(selector[:form])
+ submit = form.first('.js-dirty-submit')
+ input = form.first(selector[:input])
+ original_value = input.value
+
+ expect(submit.disabled?).to be true
+
+ input.set("#{original_value} changes")
+
+ form.find('.js-dirty-submit:not([disabled])', match: :first)
+ expect(submit.disabled?).to be false
+
+ input.set(original_value)
+
+ form.find('.js-dirty-submit[disabled]', match: :first)
+ expect(submit.disabled?).to be true
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
index 87d12a784ba..1f76b981292 100644
--- a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
@@ -11,60 +11,4 @@ shared_examples 'cluster application core specs' do |application_name|
expect(Clusters::Cluster::APPLICATIONS[subject.name]).to eq(described_class)
end
end
-
- describe 'status state machine' do
- describe '#make_installing' do
- subject { create(application_name, :scheduled) }
-
- it 'is installing' do
- subject.make_installing!
-
- expect(subject).to be_installing
- end
- end
-
- describe '#make_installed' do
- subject { create(application_name, :installing) }
-
- it 'is installed' do
- subject.make_installed
-
- expect(subject).to be_installed
- end
- end
-
- describe '#make_errored' do
- subject { create(application_name, :installing) }
- let(:reason) { 'some errors' }
-
- it 'is errored' do
- subject.make_errored(reason)
-
- expect(subject).to be_errored
- expect(subject.status_reason).to eq(reason)
- end
- end
-
- describe '#make_scheduled' do
- subject { create(application_name, :installable) }
-
- it 'is scheduled' do
- subject.make_scheduled
-
- expect(subject).to be_scheduled
- end
-
- describe 'when was errored' do
- subject { create(application_name, :errored) }
-
- it 'clears #status_reason' do
- expect(subject.status_reason).not_to be_nil
-
- subject.make_scheduled!
-
- expect(subject.status_reason).to be_nil
- end
- end
- end
- end
end
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 765dd32f4ba..82f0dd5d00f 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -28,4 +28,87 @@ shared_examples 'cluster application status specs' do |application_name|
end
end
end
+
+ describe 'status state machine' do
+ describe '#make_installing' do
+ subject { create(application_name, :scheduled) }
+
+ it 'is installing' do
+ subject.make_installing!
+
+ expect(subject).to be_installing
+ end
+ end
+
+ describe '#make_installed' do
+ subject { create(application_name, :installing) }
+
+ it 'is installed' do
+ subject.make_installed
+
+ expect(subject).to be_installed
+ end
+ end
+
+ describe '#make_errored' do
+ subject { create(application_name, :installing) }
+ let(:reason) { 'some errors' }
+
+ it 'is errored' do
+ subject.make_errored(reason)
+
+ expect(subject).to be_errored
+ expect(subject.status_reason).to eq(reason)
+ end
+ end
+
+ describe '#make_scheduled' do
+ subject { create(application_name, :installable) }
+
+ it 'is scheduled' do
+ subject.make_scheduled
+
+ expect(subject).to be_scheduled
+ end
+
+ describe 'when was errored' do
+ subject { create(application_name, :errored) }
+
+ it 'clears #status_reason' do
+ expect(subject.status_reason).not_to be_nil
+
+ subject.make_scheduled!
+
+ expect(subject.status_reason).to be_nil
+ end
+ end
+ end
+ end
+
+ describe '#available?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:trait, :available) do
+ :not_installable | false
+ :installable | false
+ :scheduled | false
+ :installing | false
+ :installed | true
+ :updating | false
+ :updated | true
+ :errored | false
+ :update_errored | false
+ :timeouted | false
+ end
+
+ with_them do
+ subject { build(application_name, trait) }
+
+ if params[:available]
+ it { is_expected.to be_available }
+ else
+ it { is_expected.not_to be_available }
+ end
+ end
+ end
end
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index 5fb9ced3b63..66536e80db2 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -45,6 +45,20 @@ shared_examples 'an email that contains a header with author username' do
end
end
+shared_examples 'an email with X-GitLab headers containing IDs' do
+ it 'has X-GitLab-*-ID header' do
+ is_expected.to have_header "X-GitLab-#{model.class.name}-ID", "#{model.id}"
+ end
+
+ it 'has X-GitLab-*-IID header if model has iid defined' do
+ if model.respond_to?(:iid)
+ is_expected.to have_header "X-GitLab-#{model.class.name}-IID", "#{model.iid}"
+ else
+ expect(subject.header["X-GitLab-#{model.class.name}-IID"]).to eq nil
+ end
+ end
+end
+
shared_examples 'an email with X-GitLab headers containing project details' do
it 'has X-GitLab-Project headers' do
aggregate_failures do
@@ -69,6 +83,7 @@ end
shared_examples 'a thread answer email with reply-by-email enabled' do
include_examples 'an email with X-GitLab headers containing project details'
+ include_examples 'an email with X-GitLab headers containing IDs'
it 'has the characteristics of a threaded reply' do
host = Gitlab.config.gitlab.host
@@ -85,6 +100,7 @@ end
shared_examples 'an email starting a new thread with reply-by-email enabled' do
include_examples 'an email with X-GitLab headers containing project details'
+ include_examples 'an email with X-GitLab headers containing IDs'
include_examples 'a new thread email with reply-by-email enabled'
it 'includes "Reply to this email directly or <View it on GitLab>"' do
@@ -109,6 +125,7 @@ end
shared_examples 'an answer to an existing thread with reply-by-email enabled' do
include_examples 'an email with X-GitLab headers containing project details'
+ include_examples 'an email with X-GitLab headers containing IDs'
include_examples 'a thread answer email with reply-by-email enabled'
context 'when reply-by-email is enabled with incoming address with %{key}' do
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index 26f823cb6ef..6a9ad43941d 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -7,24 +7,5 @@ RSpec.configure do |config|
allow(Gitlab::GitalyClient).to receive(:call) do
raise GRPC::Unavailable.new('Gitaly broken in this spec')
end
-
- # Track the maximum number of failures
- first_failure = Time.parse("2017-11-14 17:52:30")
- last_failure = Time.parse("2017-11-14 18:54:37")
- failure_count = Gitlab::CurrentSettings.circuitbreaker_failure_count_threshold + 1
- cache_key = "#{Gitlab::Git::Storage::REDIS_KEY_PREFIX}broken:#{Gitlab::Environment.hostname}"
-
- Gitlab::Git::Storage.redis.with do |redis|
- redis.pipelined do
- redis.zadd(Gitlab::Git::Storage::REDIS_KNOWN_KEYS, 0, cache_key)
- redis.hset(cache_key, :first_failure, first_failure.to_i)
- redis.hset(cache_key, :last_failure, last_failure.to_i)
- redis.hset(cache_key, :failure_count, failure_count.to_i)
- end
- end
- end
-
- config.after(:each, :broken_storage) do
- Gitlab::Git::Storage.redis.with(&:flushall)
end
end
diff --git a/vendor/languages.yml b/vendor/languages.yml
new file mode 100755
index 00000000000..a6edb0415b2
--- /dev/null
+++ b/vendor/languages.yml
@@ -0,0 +1,5488 @@
+# Extracted from https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
+
+# Defines all Languages known to GitHub.
+#
+# type - Either data, programming, markup, prose, or nil
+# aliases - An Array of additional aliases (implicitly
+# includes name.downcase)
+# ace_mode - A String name of the Ace Mode used for highlighting whenever
+# a file is edited. This must match one of the filenames in http://git.io/3XO_Cg.
+# Use "text" if a mode does not exist.
+# codemirror_mode - A String name of the CodeMirror Mode used for highlighting whenever a file is edited.
+# This must match a mode from https://git.io/vi9Fx
+# codemirror_mime_type - A String name of the file mime type used for highlighting whenever a file is edited.
+# This should match the `mime` associated with the mode from https://git.io/f4SoQ
+# wrap - Boolean wrap to enable line wrapping (default: false)
+# extensions - An Array of associated extensions (the first one is
+# considered the primary extension, the others should be
+# listed alphabetically)
+# filenames - An Array of filenames commonly associated with the language
+# interpreters - An Array of associated interpreters
+# searchable - Boolean flag to enable searching (defaults to true)
+# language_id - Integer used as a language-name-independent indexed field so that we can rename
+# languages in Linguist without reindexing all the code on GitHub. Must not be
+# changed for existing languages without the explicit permission of GitHub staff.
+# color - CSS hex color to represent the language. Only used if type is "programming" or "prose".
+# tm_scope - The TextMate scope that represents this programming
+# language. This should match one of the scopes listed in
+# the grammars.yml file. Use "none" if there is no grammar
+# for this language.
+# group - Name of the parent language. Languages in a group are counted
+# in the statistics as the parent language.
+#
+# Any additions or modifications (even trivial) should have corresponding
+# test changes in `test/test_blob.rb`.
+#
+# Please keep this list alphabetized. Capitalization comes before lowercase.
+
+---
+1C Enterprise:
+ type: programming
+ color: "#814CCC"
+ extensions:
+ - ".bsl"
+ - ".os"
+ tm_scope: source.bsl
+ ace_mode: text
+ language_id: 0
+ABAP:
+ type: programming
+ color: "#E8274B"
+ extensions:
+ - ".abap"
+ ace_mode: abap
+ language_id: 1
+ABNF:
+ type: data
+ ace_mode: text
+ extensions:
+ - ".abnf"
+ tm_scope: source.abnf
+ language_id: 429
+AGS Script:
+ type: programming
+ color: "#B9D9FF"
+ aliases:
+ - ags
+ extensions:
+ - ".asc"
+ - ".ash"
+ tm_scope: source.c++
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-c++src
+ language_id: 2
+AMPL:
+ type: programming
+ color: "#E6EFBB"
+ extensions:
+ - ".ampl"
+ - ".mod"
+ tm_scope: source.ampl
+ ace_mode: text
+ language_id: 3
+ANTLR:
+ type: programming
+ color: "#9DC3FF"
+ extensions:
+ - ".g4"
+ ace_mode: text
+ language_id: 4
+API Blueprint:
+ type: markup
+ color: "#2ACCA8"
+ ace_mode: markdown
+ extensions:
+ - ".apib"
+ tm_scope: text.html.markdown.source.gfm.apib
+ language_id: 5
+APL:
+ type: programming
+ color: "#5A8164"
+ extensions:
+ - ".apl"
+ - ".dyalog"
+ interpreters:
+ - apl
+ - aplx
+ - dyalog
+ tm_scope: source.apl
+ ace_mode: text
+ codemirror_mode: apl
+ codemirror_mime_type: text/apl
+ language_id: 6
+ASN.1:
+ type: data
+ extensions:
+ - ".asn"
+ - ".asn1"
+ tm_scope: source.asn
+ ace_mode: text
+ codemirror_mode: asn.1
+ codemirror_mime_type: text/x-ttcn-asn
+ language_id: 7
+ASP:
+ type: programming
+ color: "#6a40fd"
+ tm_scope: text.html.asp
+ aliases:
+ - aspx
+ - aspx-vb
+ extensions:
+ - ".asp"
+ - ".asax"
+ - ".ascx"
+ - ".ashx"
+ - ".asmx"
+ - ".aspx"
+ - ".axd"
+ ace_mode: text
+ codemirror_mode: htmlembedded
+ codemirror_mime_type: application/x-aspx
+ language_id: 8
+ATS:
+ type: programming
+ color: "#1ac620"
+ aliases:
+ - ats2
+ extensions:
+ - ".dats"
+ - ".hats"
+ - ".sats"
+ tm_scope: source.ats
+ ace_mode: ocaml
+ language_id: 9
+ActionScript:
+ type: programming
+ tm_scope: source.actionscript.3
+ color: "#882B0F"
+ aliases:
+ - actionscript 3
+ - actionscript3
+ - as3
+ extensions:
+ - ".as"
+ ace_mode: actionscript
+ language_id: 10
+Ada:
+ type: programming
+ color: "#02f88c"
+ extensions:
+ - ".adb"
+ - ".ada"
+ - ".ads"
+ aliases:
+ - ada95
+ - ada2005
+ ace_mode: ada
+ language_id: 11
+Adobe Font Metrics:
+ type: data
+ tm_scope: source.afm
+ extensions:
+ - ".afm"
+ aliases:
+ - acfm
+ - adobe composite font metrics
+ - adobe multiple font metrics
+ - amfm
+ ace_mode: text
+ language_id: 147198098
+Agda:
+ type: programming
+ color: "#315665"
+ extensions:
+ - ".agda"
+ ace_mode: text
+ language_id: 12
+Alloy:
+ type: programming
+ color: "#64C800"
+ extensions:
+ - ".als"
+ ace_mode: text
+ language_id: 13
+Alpine Abuild:
+ type: programming
+ group: Shell
+ aliases:
+ - abuild
+ - apkbuild
+ filenames:
+ - APKBUILD
+ tm_scope: source.shell
+ ace_mode: sh
+ codemirror_mode: shell
+ codemirror_mime_type: text/x-sh
+ language_id: 14
+AngelScript:
+ type: programming
+ color: "#C7D7DC"
+ extensions:
+ - ".as"
+ - ".angelscript"
+ tm_scope: source.angelscript
+ ace_mode: text
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-c++src
+ language_id: 389477596
+Ant Build System:
+ type: data
+ tm_scope: text.xml.ant
+ filenames:
+ - ant.xml
+ - build.xml
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: application/xml
+ language_id: 15
+ApacheConf:
+ type: data
+ aliases:
+ - aconf
+ - apache
+ extensions:
+ - ".apacheconf"
+ - ".vhost"
+ filenames:
+ - ".htaccess"
+ - apache2.conf
+ - httpd.conf
+ tm_scope: source.apache-config
+ ace_mode: apache_conf
+ language_id: 16
+Apex:
+ type: programming
+ extensions:
+ - ".cls"
+ tm_scope: source.java
+ ace_mode: java
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-java
+ language_id: 17
+Apollo Guidance Computer:
+ type: programming
+ group: Assembly
+ extensions:
+ - ".agc"
+ tm_scope: source.agc
+ ace_mode: assembly_x86
+ language_id: 18
+AppleScript:
+ type: programming
+ aliases:
+ - osascript
+ extensions:
+ - ".applescript"
+ - ".scpt"
+ interpreters:
+ - osascript
+ ace_mode: applescript
+ color: "#101F1F"
+ language_id: 19
+Arc:
+ type: programming
+ color: "#aa2afe"
+ extensions:
+ - ".arc"
+ tm_scope: none
+ ace_mode: text
+ language_id: 20
+AsciiDoc:
+ type: prose
+ ace_mode: asciidoc
+ wrap: true
+ extensions:
+ - ".asciidoc"
+ - ".adoc"
+ - ".asc"
+ tm_scope: text.html.asciidoc
+ language_id: 22
+AspectJ:
+ type: programming
+ color: "#a957b0"
+ extensions:
+ - ".aj"
+ tm_scope: source.aspectj
+ ace_mode: text
+ language_id: 23
+Assembly:
+ type: programming
+ color: "#6E4C13"
+ aliases:
+ - asm
+ - nasm
+ extensions:
+ - ".asm"
+ - ".a51"
+ - ".inc"
+ - ".nasm"
+ tm_scope: source.assembly
+ ace_mode: assembly_x86
+ language_id: 24
+Augeas:
+ type: programming
+ extensions:
+ - ".aug"
+ tm_scope: none
+ ace_mode: text
+ language_id: 25
+AutoHotkey:
+ type: programming
+ color: "#6594b9"
+ aliases:
+ - ahk
+ extensions:
+ - ".ahk"
+ - ".ahkl"
+ tm_scope: source.ahk
+ ace_mode: autohotkey
+ language_id: 26
+AutoIt:
+ type: programming
+ color: "#1C3552"
+ aliases:
+ - au3
+ - AutoIt3
+ - AutoItScript
+ extensions:
+ - ".au3"
+ tm_scope: source.autoit
+ ace_mode: autohotkey
+ language_id: 27
+Awk:
+ type: programming
+ extensions:
+ - ".awk"
+ - ".auk"
+ - ".gawk"
+ - ".mawk"
+ - ".nawk"
+ interpreters:
+ - awk
+ - gawk
+ - mawk
+ - nawk
+ ace_mode: text
+ language_id: 28
+Ballerina:
+ type: programming
+ extensions:
+ - ".bal"
+ tm_scope: source.ballerina
+ ace_mode: text
+ color: "#FF5000"
+ language_id: 720859680
+Batchfile:
+ type: programming
+ aliases:
+ - bat
+ - batch
+ - dosbatch
+ - winbatch
+ extensions:
+ - ".bat"
+ - ".cmd"
+ tm_scope: source.batchfile
+ ace_mode: batchfile
+ color: "#C1F12E"
+ language_id: 29
+Befunge:
+ type: programming
+ extensions:
+ - ".befunge"
+ ace_mode: text
+ language_id: 30
+Bison:
+ type: programming
+ group: Yacc
+ tm_scope: source.bison
+ extensions:
+ - ".bison"
+ ace_mode: text
+ language_id: 31
+BitBake:
+ type: programming
+ tm_scope: none
+ extensions:
+ - ".bb"
+ ace_mode: text
+ language_id: 32
+Blade:
+ type: markup
+ group: HTML
+ extensions:
+ - ".blade"
+ - ".blade.php"
+ tm_scope: text.html.php.blade
+ ace_mode: text
+ language_id: 33
+BlitzBasic:
+ type: programming
+ aliases:
+ - b3d
+ - blitz3d
+ - blitzplus
+ - bplus
+ extensions:
+ - ".bb"
+ - ".decls"
+ tm_scope: source.blitzmax
+ ace_mode: text
+ language_id: 34
+BlitzMax:
+ type: programming
+ color: "#cd6400"
+ extensions:
+ - ".bmx"
+ aliases:
+ - bmax
+ ace_mode: text
+ language_id: 35
+Bluespec:
+ type: programming
+ extensions:
+ - ".bsv"
+ tm_scope: source.bsv
+ ace_mode: verilog
+ language_id: 36
+Boo:
+ type: programming
+ color: "#d4bec1"
+ extensions:
+ - ".boo"
+ ace_mode: text
+ tm_scope: source.boo
+ language_id: 37
+Brainfuck:
+ type: programming
+ color: "#2F2530"
+ extensions:
+ - ".b"
+ - ".bf"
+ tm_scope: source.bf
+ ace_mode: text
+ codemirror_mode: brainfuck
+ codemirror_mime_type: text/x-brainfuck
+ language_id: 38
+Brightscript:
+ type: programming
+ extensions:
+ - ".brs"
+ tm_scope: source.brightscript
+ ace_mode: text
+ language_id: 39
+Bro:
+ type: programming
+ extensions:
+ - ".bro"
+ ace_mode: text
+ language_id: 40
+C:
+ type: programming
+ color: "#555555"
+ extensions:
+ - ".c"
+ - ".cats"
+ - ".h"
+ - ".idc"
+ interpreters:
+ - tcc
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csrc
+ language_id: 41
+C#:
+ type: programming
+ ace_mode: csharp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csharp
+ tm_scope: source.cs
+ color: "#178600"
+ aliases:
+ - csharp
+ extensions:
+ - ".cs"
+ - ".cake"
+ - ".cshtml"
+ - ".csx"
+ language_id: 42
+C++:
+ type: programming
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-c++src
+ color: "#f34b7d"
+ aliases:
+ - cpp
+ extensions:
+ - ".cpp"
+ - ".c++"
+ - ".cc"
+ - ".cp"
+ - ".cxx"
+ - ".h"
+ - ".h++"
+ - ".hh"
+ - ".hpp"
+ - ".hxx"
+ - ".inc"
+ - ".inl"
+ - ".ino"
+ - ".ipp"
+ - ".re"
+ - ".tcc"
+ - ".tpp"
+ language_id: 43
+C-ObjDump:
+ type: data
+ extensions:
+ - ".c-objdump"
+ tm_scope: objdump.x86asm
+ ace_mode: assembly_x86
+ language_id: 44
+C2hs Haskell:
+ type: programming
+ group: Haskell
+ aliases:
+ - c2hs
+ extensions:
+ - ".chs"
+ tm_scope: source.haskell
+ ace_mode: haskell
+ codemirror_mode: haskell
+ codemirror_mime_type: text/x-haskell
+ language_id: 45
+CLIPS:
+ type: programming
+ extensions:
+ - ".clp"
+ tm_scope: source.clips
+ ace_mode: text
+ language_id: 46
+CMake:
+ type: programming
+ extensions:
+ - ".cmake"
+ - ".cmake.in"
+ filenames:
+ - CMakeLists.txt
+ ace_mode: text
+ codemirror_mode: cmake
+ codemirror_mime_type: text/x-cmake
+ language_id: 47
+COBOL:
+ type: programming
+ extensions:
+ - ".cob"
+ - ".cbl"
+ - ".ccp"
+ - ".cobol"
+ - ".cpy"
+ ace_mode: cobol
+ codemirror_mode: cobol
+ codemirror_mime_type: text/x-cobol
+ language_id: 48
+COLLADA:
+ type: data
+ extensions:
+ - ".dae"
+ tm_scope: text.xml
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ language_id: 49
+CSON:
+ type: data
+ group: CoffeeScript
+ tm_scope: source.coffee
+ ace_mode: coffee
+ codemirror_mode: coffeescript
+ codemirror_mime_type: text/x-coffeescript
+ searchable: false
+ extensions:
+ - ".cson"
+ language_id: 424
+CSS:
+ type: markup
+ tm_scope: source.css
+ ace_mode: css
+ codemirror_mode: css
+ codemirror_mime_type: text/css
+ color: "#563d7c"
+ extensions:
+ - ".css"
+ language_id: 50
+CSV:
+ type: data
+ ace_mode: text
+ tm_scope: none
+ extensions:
+ - ".csv"
+ language_id: 51
+CWeb:
+ type: programming
+ extensions:
+ - ".w"
+ tm_scope: none
+ ace_mode: text
+ language_id: 657332628
+Cap'n Proto:
+ type: programming
+ tm_scope: source.capnp
+ extensions:
+ - ".capnp"
+ ace_mode: text
+ language_id: 52
+CartoCSS:
+ type: programming
+ aliases:
+ - Carto
+ extensions:
+ - ".mss"
+ ace_mode: text
+ tm_scope: source.css.mss
+ language_id: 53
+Ceylon:
+ type: programming
+ color: "#dfa535"
+ extensions:
+ - ".ceylon"
+ tm_scope: source.ceylon
+ ace_mode: text
+ language_id: 54
+Chapel:
+ type: programming
+ color: "#8dc63f"
+ aliases:
+ - chpl
+ extensions:
+ - ".chpl"
+ ace_mode: text
+ language_id: 55
+Charity:
+ type: programming
+ extensions:
+ - ".ch"
+ tm_scope: none
+ ace_mode: text
+ language_id: 56
+ChucK:
+ type: programming
+ extensions:
+ - ".ck"
+ tm_scope: source.java
+ ace_mode: java
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-java
+ language_id: 57
+Cirru:
+ type: programming
+ color: "#ccccff"
+ ace_mode: cirru
+ extensions:
+ - ".cirru"
+ language_id: 58
+Clarion:
+ type: programming
+ color: "#db901e"
+ ace_mode: text
+ extensions:
+ - ".clw"
+ tm_scope: source.clarion
+ language_id: 59
+Clean:
+ type: programming
+ color: "#3F85AF"
+ extensions:
+ - ".icl"
+ - ".dcl"
+ tm_scope: source.clean
+ ace_mode: text
+ language_id: 60
+Click:
+ type: programming
+ color: "#E4E6F3"
+ extensions:
+ - ".click"
+ tm_scope: source.click
+ ace_mode: text
+ language_id: 61
+Clojure:
+ type: programming
+ ace_mode: clojure
+ codemirror_mode: clojure
+ codemirror_mime_type: text/x-clojure
+ color: "#db5855"
+ extensions:
+ - ".clj"
+ - ".boot"
+ - ".cl2"
+ - ".cljc"
+ - ".cljs"
+ - ".cljs.hl"
+ - ".cljscm"
+ - ".cljx"
+ - ".hic"
+ filenames:
+ - riemann.config
+ language_id: 62
+Closure Templates:
+ type: markup
+ group: HTML
+ ace_mode: soy_template
+ codemirror_mode: soy
+ codemirror_mime_type: text/x-soy
+ aliases:
+ - soy
+ extensions:
+ - ".soy"
+ tm_scope: text.html.soy
+ language_id: 357046146
+Cloud Firestore Security Rules:
+ type: data
+ ace_mode: less
+ codemirror_mode: css
+ codemirror_mime_type: text/css
+ tm_scope: source.firestore
+ filenames:
+ - firestore.rules
+ language_id: 407996372
+CoNLL-U:
+ type: data
+ extensions:
+ - ".conllu"
+ - ".conll"
+ tm_scope: text.conllu
+ ace_mode: text
+ aliases:
+ - CoNLL
+ - CoNLL-X
+ language_id: 421026389
+CoffeeScript:
+ type: programming
+ tm_scope: source.coffee
+ ace_mode: coffee
+ codemirror_mode: coffeescript
+ codemirror_mime_type: text/x-coffeescript
+ color: "#244776"
+ aliases:
+ - coffee
+ - coffee-script
+ extensions:
+ - ".coffee"
+ - "._coffee"
+ - ".cake"
+ - ".cjsx"
+ - ".iced"
+ filenames:
+ - Cakefile
+ interpreters:
+ - coffee
+ language_id: 63
+ColdFusion:
+ type: programming
+ ace_mode: coldfusion
+ color: "#ed2cd6"
+ aliases:
+ - cfm
+ - cfml
+ - coldfusion html
+ extensions:
+ - ".cfm"
+ - ".cfml"
+ tm_scope: text.html.cfm
+ language_id: 64
+ColdFusion CFC:
+ type: programming
+ group: ColdFusion
+ ace_mode: coldfusion
+ aliases:
+ - cfc
+ extensions:
+ - ".cfc"
+ tm_scope: source.cfscript
+ language_id: 65
+Common Lisp:
+ type: programming
+ tm_scope: source.lisp
+ color: "#3fb68b"
+ aliases:
+ - lisp
+ extensions:
+ - ".lisp"
+ - ".asd"
+ - ".cl"
+ - ".l"
+ - ".lsp"
+ - ".ny"
+ - ".podsl"
+ - ".sexp"
+ interpreters:
+ - lisp
+ - sbcl
+ - ccl
+ - clisp
+ - ecl
+ ace_mode: lisp
+ codemirror_mode: commonlisp
+ codemirror_mime_type: text/x-common-lisp
+ language_id: 66
+Common Workflow Language:
+ aliases:
+ - cwl
+ type: programming
+ ace_mode: yaml
+ codemirror_mode: yaml
+ codemirror_mime_type: text/x-yaml
+ extensions:
+ - ".cwl"
+ interpreters:
+ - cwl-runner
+ color: "#B5314C"
+ tm_scope: source.cwl
+ language_id: 988547172
+Component Pascal:
+ type: programming
+ color: "#B0CE4E"
+ extensions:
+ - ".cp"
+ - ".cps"
+ tm_scope: source.pascal
+ aliases:
+ - delphi
+ - objectpascal
+ ace_mode: pascal
+ codemirror_mode: pascal
+ codemirror_mime_type: text/x-pascal
+ language_id: 67
+Cool:
+ type: programming
+ extensions:
+ - ".cl"
+ tm_scope: source.cool
+ ace_mode: text
+ language_id: 68
+Coq:
+ type: programming
+ extensions:
+ - ".coq"
+ - ".v"
+ ace_mode: text
+ language_id: 69
+Cpp-ObjDump:
+ type: data
+ extensions:
+ - ".cppobjdump"
+ - ".c++-objdump"
+ - ".c++objdump"
+ - ".cpp-objdump"
+ - ".cxx-objdump"
+ tm_scope: objdump.x86asm
+ aliases:
+ - c++-objdump
+ ace_mode: assembly_x86
+ language_id: 70
+Creole:
+ type: prose
+ wrap: true
+ extensions:
+ - ".creole"
+ tm_scope: text.html.creole
+ ace_mode: text
+ language_id: 71
+Crystal:
+ type: programming
+ color: "#776791"
+ extensions:
+ - ".cr"
+ ace_mode: ruby
+ codemirror_mode: crystal
+ codemirror_mime_type: text/x-crystal
+ tm_scope: source.crystal
+ interpreters:
+ - crystal
+ language_id: 72
+Csound:
+ type: programming
+ aliases:
+ - csound-orc
+ extensions:
+ - ".orc"
+ - ".udo"
+ tm_scope: source.csound
+ ace_mode: csound_orchestra
+ language_id: 73
+Csound Document:
+ type: programming
+ aliases:
+ - csound-csd
+ extensions:
+ - ".csd"
+ tm_scope: source.csound-document
+ ace_mode: csound_document
+ language_id: 74
+Csound Score:
+ type: programming
+ aliases:
+ - csound-sco
+ extensions:
+ - ".sco"
+ tm_scope: source.csound-score
+ ace_mode: csound_score
+ language_id: 75
+Cuda:
+ type: programming
+ extensions:
+ - ".cu"
+ - ".cuh"
+ tm_scope: source.cuda-c++
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-c++src
+ color: "#3A4E3A"
+ language_id: 77
+Cycript:
+ type: programming
+ extensions:
+ - ".cy"
+ tm_scope: source.js
+ ace_mode: javascript
+ codemirror_mode: javascript
+ codemirror_mime_type: text/javascript
+ language_id: 78
+Cython:
+ type: programming
+ group: Python
+ extensions:
+ - ".pyx"
+ - ".pxd"
+ - ".pxi"
+ aliases:
+ - pyrex
+ ace_mode: text
+ codemirror_mode: python
+ codemirror_mime_type: text/x-cython
+ language_id: 79
+D:
+ type: programming
+ color: "#ba595e"
+ extensions:
+ - ".d"
+ - ".di"
+ ace_mode: d
+ codemirror_mode: d
+ codemirror_mime_type: text/x-d
+ language_id: 80
+D-ObjDump:
+ type: data
+ extensions:
+ - ".d-objdump"
+ tm_scope: objdump.x86asm
+ ace_mode: assembly_x86
+ language_id: 81
+DIGITAL Command Language:
+ type: programming
+ aliases:
+ - dcl
+ extensions:
+ - ".com"
+ tm_scope: none
+ ace_mode: text
+ language_id: 82
+DM:
+ type: programming
+ color: "#447265"
+ extensions:
+ - ".dm"
+ aliases:
+ - byond
+ tm_scope: source.dm
+ ace_mode: c_cpp
+ language_id: 83
+DNS Zone:
+ type: data
+ extensions:
+ - ".zone"
+ - ".arpa"
+ tm_scope: text.zone_file
+ ace_mode: text
+ language_id: 84
+DTrace:
+ type: programming
+ aliases:
+ - dtrace-script
+ extensions:
+ - ".d"
+ interpreters:
+ - dtrace
+ tm_scope: source.c
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csrc
+ language_id: 85
+Darcs Patch:
+ type: data
+ aliases:
+ - dpatch
+ extensions:
+ - ".darcspatch"
+ - ".dpatch"
+ tm_scope: none
+ ace_mode: text
+ language_id: 86
+Dart:
+ type: programming
+ color: "#00B4AB"
+ extensions:
+ - ".dart"
+ interpreters:
+ - dart
+ ace_mode: dart
+ codemirror_mode: dart
+ codemirror_mime_type: application/dart
+ language_id: 87
+DataWeave:
+ type: programming
+ color: "#003a52"
+ extensions:
+ - ".dwl"
+ ace_mode: text
+ tm_scope: source.data-weave
+ language_id: 974514097
+Diff:
+ type: data
+ extensions:
+ - ".diff"
+ - ".patch"
+ aliases:
+ - udiff
+ tm_scope: source.diff
+ ace_mode: diff
+ codemirror_mode: diff
+ codemirror_mime_type: text/x-diff
+ language_id: 88
+Dockerfile:
+ type: programming
+ color: "#0db7ed"
+ tm_scope: source.dockerfile
+ extensions:
+ - ".dockerfile"
+ filenames:
+ - Dockerfile
+ ace_mode: dockerfile
+ codemirror_mode: dockerfile
+ codemirror_mime_type: text/x-dockerfile
+ language_id: 89
+Dogescript:
+ type: programming
+ color: "#cca760"
+ extensions:
+ - ".djs"
+ tm_scope: none
+ ace_mode: text
+ language_id: 90
+Dylan:
+ type: programming
+ color: "#6c616e"
+ extensions:
+ - ".dylan"
+ - ".dyl"
+ - ".intr"
+ - ".lid"
+ ace_mode: text
+ codemirror_mode: dylan
+ codemirror_mime_type: text/x-dylan
+ language_id: 91
+E:
+ type: programming
+ color: "#ccce35"
+ extensions:
+ - ".E"
+ interpreters:
+ - rune
+ tm_scope: none
+ ace_mode: text
+ language_id: 92
+EBNF:
+ type: data
+ extensions:
+ - ".ebnf"
+ tm_scope: source.ebnf
+ ace_mode: text
+ codemirror_mode: ebnf
+ codemirror_mime_type: text/x-ebnf
+ language_id: 430
+ECL:
+ type: programming
+ color: "#8a1267"
+ extensions:
+ - ".ecl"
+ - ".eclxml"
+ tm_scope: none
+ ace_mode: text
+ codemirror_mode: ecl
+ codemirror_mime_type: text/x-ecl
+ language_id: 93
+ECLiPSe:
+ type: programming
+ group: prolog
+ extensions:
+ - ".ecl"
+ tm_scope: source.prolog.eclipse
+ ace_mode: prolog
+ language_id: 94
+EJS:
+ type: markup
+ group: HTML
+ extensions:
+ - ".ejs"
+ tm_scope: text.html.js
+ ace_mode: ejs
+ language_id: 95
+EQ:
+ type: programming
+ color: "#a78649"
+ extensions:
+ - ".eq"
+ tm_scope: source.cs
+ ace_mode: csharp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csharp
+ language_id: 96
+Eagle:
+ type: data
+ extensions:
+ - ".sch"
+ - ".brd"
+ tm_scope: text.xml
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ language_id: 97
+Easybuild:
+ type: data
+ group: Python
+ ace_mode: python
+ codemirror_mode: python
+ codemirror_mime_type: text/x-python
+ tm_scope: source.python
+ extensions:
+ - ".eb"
+ language_id: 342840477
+Ecere Projects:
+ type: data
+ group: JavaScript
+ extensions:
+ - ".epj"
+ tm_scope: source.json
+ ace_mode: json
+ codemirror_mode: javascript
+ codemirror_mime_type: application/json
+ language_id: 98
+Edje Data Collection:
+ type: data
+ extensions:
+ - ".edc"
+ tm_scope: source.json
+ ace_mode: json
+ codemirror_mode: javascript
+ codemirror_mime_type: application/json
+ language_id: 342840478
+Eiffel:
+ type: programming
+ color: "#946d57"
+ extensions:
+ - ".e"
+ ace_mode: eiffel
+ codemirror_mode: eiffel
+ codemirror_mime_type: text/x-eiffel
+ language_id: 99
+Elixir:
+ type: programming
+ color: "#6e4a7e"
+ extensions:
+ - ".ex"
+ - ".exs"
+ ace_mode: elixir
+ filenames:
+ - mix.lock
+ interpreters:
+ - elixir
+ language_id: 100
+Elm:
+ type: programming
+ color: "#60B5CC"
+ extensions:
+ - ".elm"
+ tm_scope: source.elm
+ ace_mode: elm
+ codemirror_mode: elm
+ codemirror_mime_type: text/x-elm
+ language_id: 101
+Emacs Lisp:
+ type: programming
+ tm_scope: source.emacs.lisp
+ color: "#c065db"
+ aliases:
+ - elisp
+ - emacs
+ filenames:
+ - ".abbrev_defs"
+ - ".emacs"
+ - ".emacs.desktop"
+ - ".gnus"
+ - ".spacemacs"
+ - ".viper"
+ - Cask
+ - Project.ede
+ - _emacs
+ - abbrev_defs
+ extensions:
+ - ".el"
+ - ".emacs"
+ - ".emacs.desktop"
+ ace_mode: lisp
+ codemirror_mode: commonlisp
+ codemirror_mime_type: text/x-common-lisp
+ language_id: 102
+EmberScript:
+ type: programming
+ color: "#FFF4F3"
+ extensions:
+ - ".em"
+ - ".emberscript"
+ tm_scope: source.coffee
+ ace_mode: coffee
+ codemirror_mode: coffeescript
+ codemirror_mime_type: text/x-coffeescript
+ language_id: 103
+Erlang:
+ type: programming
+ color: "#B83998"
+ extensions:
+ - ".erl"
+ - ".app.src"
+ - ".es"
+ - ".escript"
+ - ".hrl"
+ - ".xrl"
+ - ".yrl"
+ filenames:
+ - Emakefile
+ - rebar.config
+ - rebar.config.lock
+ - rebar.lock
+ ace_mode: erlang
+ codemirror_mode: erlang
+ codemirror_mime_type: text/x-erlang
+ interpreters:
+ - escript
+ language_id: 104
+F#:
+ type: programming
+ color: "#b845fc"
+ aliases:
+ - fsharp
+ extensions:
+ - ".fs"
+ - ".fsi"
+ - ".fsx"
+ tm_scope: source.fsharp
+ ace_mode: text
+ codemirror_mode: mllike
+ codemirror_mime_type: text/x-fsharp
+ language_id: 105
+FLUX:
+ type: programming
+ color: "#88ccff"
+ extensions:
+ - ".fx"
+ - ".flux"
+ tm_scope: none
+ ace_mode: text
+ language_id: 106
+Factor:
+ type: programming
+ color: "#636746"
+ extensions:
+ - ".factor"
+ filenames:
+ - ".factor-boot-rc"
+ - ".factor-rc"
+ ace_mode: text
+ codemirror_mode: factor
+ codemirror_mime_type: text/x-factor
+ language_id: 108
+Fancy:
+ type: programming
+ color: "#7b9db4"
+ extensions:
+ - ".fy"
+ - ".fancypack"
+ filenames:
+ - Fakefile
+ ace_mode: text
+ language_id: 109
+Fantom:
+ type: programming
+ color: "#14253c"
+ extensions:
+ - ".fan"
+ tm_scope: source.fan
+ ace_mode: text
+ language_id: 110
+Filebench WML:
+ type: programming
+ extensions:
+ - ".f"
+ tm_scope: none
+ ace_mode: text
+ language_id: 111
+Filterscript:
+ type: programming
+ group: RenderScript
+ extensions:
+ - ".fs"
+ tm_scope: none
+ ace_mode: text
+ language_id: 112
+Formatted:
+ type: data
+ extensions:
+ - ".for"
+ - ".eam.fs"
+ tm_scope: none
+ ace_mode: text
+ language_id: 113
+Forth:
+ type: programming
+ color: "#341708"
+ extensions:
+ - ".fth"
+ - ".4th"
+ - ".f"
+ - ".for"
+ - ".forth"
+ - ".fr"
+ - ".frt"
+ - ".fs"
+ ace_mode: forth
+ codemirror_mode: forth
+ codemirror_mime_type: text/x-forth
+ language_id: 114
+Fortran:
+ type: programming
+ color: "#4d41b1"
+ extensions:
+ - ".f90"
+ - ".f"
+ - ".f03"
+ - ".f08"
+ - ".f77"
+ - ".f95"
+ - ".for"
+ - ".fpp"
+ tm_scope: source.fortran.modern
+ ace_mode: text
+ codemirror_mode: fortran
+ codemirror_mime_type: text/x-fortran
+ language_id: 107
+FreeMarker:
+ type: programming
+ color: "#0050b2"
+ aliases:
+ - ftl
+ extensions:
+ - ".ftl"
+ tm_scope: text.html.ftl
+ ace_mode: ftl
+ language_id: 115
+Frege:
+ type: programming
+ color: "#00cafe"
+ extensions:
+ - ".fr"
+ tm_scope: source.haskell
+ ace_mode: haskell
+ language_id: 116
+G-code:
+ type: data
+ extensions:
+ - ".g"
+ - ".gco"
+ - ".gcode"
+ tm_scope: source.gcode
+ ace_mode: gcode
+ language_id: 117
+GAMS:
+ type: programming
+ extensions:
+ - ".gms"
+ tm_scope: none
+ ace_mode: text
+ language_id: 118
+GAP:
+ type: programming
+ extensions:
+ - ".g"
+ - ".gap"
+ - ".gd"
+ - ".gi"
+ - ".tst"
+ tm_scope: source.gap
+ ace_mode: text
+ language_id: 119
+GCC Machine Description:
+ type: programming
+ extensions:
+ - ".md"
+ tm_scope: source.lisp
+ ace_mode: lisp
+ codemirror_mode: commonlisp
+ codemirror_mime_type: text/x-common-lisp
+ language_id: 121
+GDB:
+ type: programming
+ extensions:
+ - ".gdb"
+ - ".gdbinit"
+ tm_scope: source.gdb
+ ace_mode: text
+ language_id: 122
+GDScript:
+ type: programming
+ color: "#355570"
+ extensions:
+ - ".gd"
+ tm_scope: source.gdscript
+ ace_mode: text
+ language_id: 123
+GLSL:
+ type: programming
+ extensions:
+ - ".glsl"
+ - ".fp"
+ - ".frag"
+ - ".frg"
+ - ".fs"
+ - ".fsh"
+ - ".fshader"
+ - ".geo"
+ - ".geom"
+ - ".glslv"
+ - ".gshader"
+ - ".shader"
+ - ".tesc"
+ - ".tese"
+ - ".vert"
+ - ".vrx"
+ - ".vsh"
+ - ".vshader"
+ ace_mode: glsl
+ language_id: 124
+GN:
+ type: data
+ extensions:
+ - ".gn"
+ - ".gni"
+ interpreters:
+ - gn
+ filenames:
+ - ".gn"
+ tm_scope: source.gn
+ ace_mode: python
+ codemirror_mode: python
+ codemirror_mime_type: text/x-python
+ language_id: 302957008
+Game Maker Language:
+ type: programming
+ color: "#8fb200"
+ extensions:
+ - ".gml"
+ tm_scope: source.c++
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-c++src
+ language_id: 125
+Genie:
+ type: programming
+ ace_mode: text
+ extensions:
+ - ".gs"
+ color: "#fb855d"
+ tm_scope: none
+ language_id: 792408528
+Genshi:
+ type: programming
+ extensions:
+ - ".kid"
+ tm_scope: text.xml.genshi
+ aliases:
+ - xml+genshi
+ - xml+kid
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ language_id: 126
+Gentoo Ebuild:
+ type: programming
+ group: Shell
+ extensions:
+ - ".ebuild"
+ tm_scope: source.shell
+ ace_mode: sh
+ codemirror_mode: shell
+ codemirror_mime_type: text/x-sh
+ language_id: 127
+Gentoo Eclass:
+ type: programming
+ group: Shell
+ extensions:
+ - ".eclass"
+ tm_scope: source.shell
+ ace_mode: sh
+ codemirror_mode: shell
+ codemirror_mime_type: text/x-sh
+ language_id: 128
+Gerber Image:
+ type: data
+ aliases:
+ - rs-274x
+ extensions:
+ - ".gbr"
+ - ".gbl"
+ - ".gbo"
+ - ".gbp"
+ - ".gbs"
+ - ".gko"
+ - ".gpb"
+ - ".gpt"
+ - ".gtl"
+ - ".gto"
+ - ".gtp"
+ - ".gts"
+ interpreters:
+ - gerbv
+ - gerbview
+ tm_scope: source.gerber
+ ace_mode: text
+ language_id: 404627610
+Gettext Catalog:
+ type: prose
+ searchable: false
+ aliases:
+ - pot
+ extensions:
+ - ".po"
+ - ".pot"
+ tm_scope: source.po
+ ace_mode: text
+ language_id: 129
+Gherkin:
+ type: programming
+ extensions:
+ - ".feature"
+ tm_scope: text.gherkin.feature
+ aliases:
+ - cucumber
+ ace_mode: text
+ color: "#5B2063"
+ language_id: 76
+Glyph:
+ type: programming
+ color: "#c1ac7f"
+ extensions:
+ - ".glf"
+ tm_scope: source.tcl
+ ace_mode: tcl
+ codemirror_mode: tcl
+ codemirror_mime_type: text/x-tcl
+ language_id: 130
+Gnuplot:
+ type: programming
+ color: "#f0a9f0"
+ extensions:
+ - ".gp"
+ - ".gnu"
+ - ".gnuplot"
+ - ".plot"
+ - ".plt"
+ interpreters:
+ - gnuplot
+ ace_mode: text
+ language_id: 131
+Go:
+ type: programming
+ color: "#375eab"
+ aliases:
+ - golang
+ extensions:
+ - ".go"
+ ace_mode: golang
+ codemirror_mode: go
+ codemirror_mime_type: text/x-go
+ language_id: 132
+Golo:
+ type: programming
+ color: "#88562A"
+ extensions:
+ - ".golo"
+ tm_scope: source.golo
+ ace_mode: text
+ language_id: 133
+Gosu:
+ type: programming
+ color: "#82937f"
+ extensions:
+ - ".gs"
+ - ".gst"
+ - ".gsx"
+ - ".vark"
+ tm_scope: source.gosu.2
+ ace_mode: text
+ language_id: 134
+Grace:
+ type: programming
+ extensions:
+ - ".grace"
+ tm_scope: source.grace
+ ace_mode: text
+ language_id: 135
+Gradle:
+ type: data
+ extensions:
+ - ".gradle"
+ tm_scope: source.groovy.gradle
+ ace_mode: text
+ language_id: 136
+Grammatical Framework:
+ type: programming
+ aliases:
+ - gf
+ wrap: false
+ extensions:
+ - ".gf"
+ searchable: true
+ color: "#79aa7a"
+ tm_scope: source.haskell
+ ace_mode: haskell
+ codemirror_mode: haskell
+ codemirror_mime_type: text/x-haskell
+ language_id: 137
+Graph Modeling Language:
+ type: data
+ extensions:
+ - ".gml"
+ tm_scope: none
+ ace_mode: text
+ language_id: 138
+GraphQL:
+ type: data
+ extensions:
+ - ".graphql"
+ - ".gql"
+ tm_scope: source.graphql
+ ace_mode: text
+ language_id: 139
+Graphviz (DOT):
+ type: data
+ tm_scope: source.dot
+ extensions:
+ - ".dot"
+ - ".gv"
+ ace_mode: text
+ language_id: 140
+Groovy:
+ type: programming
+ ace_mode: groovy
+ codemirror_mode: groovy
+ codemirror_mime_type: text/x-groovy
+ color: "#e69f56"
+ extensions:
+ - ".groovy"
+ - ".grt"
+ - ".gtpl"
+ - ".gvy"
+ interpreters:
+ - groovy
+ filenames:
+ - Jenkinsfile
+ language_id: 142
+Groovy Server Pages:
+ type: programming
+ group: Groovy
+ aliases:
+ - gsp
+ - java server page
+ extensions:
+ - ".gsp"
+ tm_scope: text.html.jsp
+ ace_mode: jsp
+ codemirror_mode: htmlembedded
+ codemirror_mime_type: application/x-jsp
+ language_id: 143
+HCL:
+ type: programming
+ extensions:
+ - ".hcl"
+ - ".tf"
+ - ".tfvars"
+ ace_mode: ruby
+ codemirror_mode: ruby
+ codemirror_mime_type: text/x-ruby
+ tm_scope: source.terraform
+ language_id: 144
+HLSL:
+ type: programming
+ extensions:
+ - ".hlsl"
+ - ".cginc"
+ - ".fx"
+ - ".fxh"
+ - ".hlsli"
+ ace_mode: text
+ tm_scope: source.hlsl
+ language_id: 145
+HTML:
+ type: markup
+ tm_scope: text.html.basic
+ ace_mode: html
+ codemirror_mode: htmlmixed
+ codemirror_mime_type: text/html
+ color: "#e34c26"
+ aliases:
+ - xhtml
+ extensions:
+ - ".html"
+ - ".htm"
+ - ".html.hl"
+ - ".inc"
+ - ".st"
+ - ".xht"
+ - ".xhtml"
+ language_id: 146
+HTML+Django:
+ type: markup
+ tm_scope: text.html.django
+ group: HTML
+ extensions:
+ - ".jinja"
+ - ".jinja2"
+ - ".mustache"
+ - ".njk"
+ aliases:
+ - django
+ - html+django/jinja
+ - html+jinja
+ - htmldjango
+ - njk
+ - nunjucks
+ ace_mode: django
+ codemirror_mode: django
+ codemirror_mime_type: text/x-django
+ language_id: 147
+HTML+ECR:
+ type: markup
+ tm_scope: text.html.ecr
+ group: HTML
+ aliases:
+ - ecr
+ extensions:
+ - ".ecr"
+ ace_mode: text
+ codemirror_mode: htmlmixed
+ codemirror_mime_type: text/html
+ language_id: 148
+HTML+EEX:
+ type: markup
+ tm_scope: text.html.elixir
+ group: HTML
+ aliases:
+ - eex
+ extensions:
+ - ".eex"
+ ace_mode: text
+ codemirror_mode: htmlmixed
+ codemirror_mime_type: text/html
+ language_id: 149
+HTML+ERB:
+ type: markup
+ tm_scope: text.html.erb
+ group: HTML
+ aliases:
+ - erb
+ extensions:
+ - ".erb"
+ - ".erb.deface"
+ ace_mode: text
+ codemirror_mode: htmlembedded
+ codemirror_mime_type: application/x-erb
+ language_id: 150
+HTML+PHP:
+ type: markup
+ tm_scope: text.html.php
+ group: HTML
+ extensions:
+ - ".phtml"
+ ace_mode: php
+ codemirror_mode: php
+ codemirror_mime_type: application/x-httpd-php
+ language_id: 151
+HTTP:
+ type: data
+ extensions:
+ - ".http"
+ tm_scope: source.httpspec
+ ace_mode: text
+ codemirror_mode: http
+ codemirror_mime_type: message/http
+ language_id: 152
+HXML:
+ type: data
+ ace_mode: text
+ extensions:
+ - ".hxml"
+ tm_scope: source.hxml
+ language_id: 786683730
+Hack:
+ type: programming
+ ace_mode: php
+ codemirror_mode: php
+ codemirror_mime_type: application/x-httpd-php
+ extensions:
+ - ".hh"
+ - ".php"
+ tm_scope: text.html.php
+ color: "#878787"
+ language_id: 153
+Haml:
+ group: HTML
+ type: markup
+ extensions:
+ - ".haml"
+ - ".haml.deface"
+ ace_mode: haml
+ codemirror_mode: haml
+ codemirror_mime_type: text/x-haml
+ language_id: 154
+Handlebars:
+ type: markup
+ group: HTML
+ aliases:
+ - hbs
+ - htmlbars
+ extensions:
+ - ".handlebars"
+ - ".hbs"
+ tm_scope: text.html.handlebars
+ ace_mode: handlebars
+ language_id: 155
+Harbour:
+ type: programming
+ color: "#0e60e3"
+ extensions:
+ - ".hb"
+ tm_scope: source.harbour
+ ace_mode: text
+ language_id: 156
+Haskell:
+ type: programming
+ color: "#5e5086"
+ extensions:
+ - ".hs"
+ - ".hsc"
+ interpreters:
+ - runhaskell
+ ace_mode: haskell
+ codemirror_mode: haskell
+ codemirror_mime_type: text/x-haskell
+ language_id: 157
+Haxe:
+ type: programming
+ ace_mode: haxe
+ codemirror_mode: haxe
+ codemirror_mime_type: text/x-haxe
+ color: "#df7900"
+ extensions:
+ - ".hx"
+ - ".hxsl"
+ tm_scope: source.hx
+ language_id: 158
+HiveQL:
+ type: programming
+ extensions:
+ - ".q"
+ color: "#dce200"
+ tm_scope: source.hql
+ ace_mode: sql
+ language_id: 931814087
+Hy:
+ type: programming
+ ace_mode: text
+ color: "#7790B2"
+ extensions:
+ - ".hy"
+ interpreters:
+ - "hy"
+ aliases:
+ - hylang
+ tm_scope: none
+ language_id: 159
+HyPhy:
+ type: programming
+ ace_mode: text
+ extensions:
+ - ".bf"
+ tm_scope: none
+ language_id: 160
+IDL:
+ type: programming
+ color: "#a3522f"
+ extensions:
+ - ".pro"
+ - ".dlm"
+ ace_mode: text
+ codemirror_mode: idl
+ codemirror_mime_type: text/x-idl
+ language_id: 161
+IGOR Pro:
+ type: programming
+ extensions:
+ - ".ipf"
+ aliases:
+ - igor
+ - igorpro
+ tm_scope: none
+ ace_mode: text
+ language_id: 162
+INI:
+ type: data
+ extensions:
+ - ".ini"
+ - ".cfg"
+ - ".prefs"
+ - ".pro"
+ - ".properties"
+ filenames:
+ - ".editorconfig"
+ - ".gitconfig"
+ - buildozer.spec
+ tm_scope: source.ini
+ aliases:
+ - dosini
+ ace_mode: ini
+ codemirror_mode: properties
+ codemirror_mime_type: text/x-properties
+ language_id: 163
+IRC log:
+ type: data
+ aliases:
+ - irc
+ - irc logs
+ extensions:
+ - ".irclog"
+ - ".weechatlog"
+ tm_scope: none
+ ace_mode: text
+ codemirror_mode: mirc
+ codemirror_mime_type: text/mirc
+ language_id: 164
+Idris:
+ type: programming
+ color: "#b30000"
+ extensions:
+ - ".idr"
+ - ".lidr"
+ ace_mode: text
+ tm_scope: source.idris
+ language_id: 165
+Inform 7:
+ type: programming
+ wrap: true
+ extensions:
+ - ".ni"
+ - ".i7x"
+ tm_scope: source.inform7
+ aliases:
+ - i7
+ - inform7
+ ace_mode: text
+ language_id: 166
+Inno Setup:
+ type: programming
+ extensions:
+ - ".iss"
+ tm_scope: none
+ ace_mode: text
+ language_id: 167
+Io:
+ type: programming
+ color: "#a9188d"
+ extensions:
+ - ".io"
+ interpreters:
+ - io
+ ace_mode: io
+ language_id: 168
+Ioke:
+ type: programming
+ color: "#078193"
+ extensions:
+ - ".ik"
+ interpreters:
+ - ioke
+ ace_mode: text
+ language_id: 169
+Isabelle:
+ type: programming
+ color: "#FEFE00"
+ extensions:
+ - ".thy"
+ tm_scope: source.isabelle.theory
+ ace_mode: text
+ language_id: 170
+Isabelle ROOT:
+ type: programming
+ group: Isabelle
+ filenames:
+ - ROOT
+ tm_scope: source.isabelle.root
+ ace_mode: text
+ language_id: 171
+J:
+ type: programming
+ color: "#9EEDFF"
+ extensions:
+ - ".ijs"
+ interpreters:
+ - jconsole
+ tm_scope: source.j
+ ace_mode: text
+ language_id: 172
+JFlex:
+ type: programming
+ group: Lex
+ extensions:
+ - ".flex"
+ - ".jflex"
+ tm_scope: source.jflex
+ ace_mode: text
+ language_id: 173
+JSON:
+ type: data
+ tm_scope: source.json
+ group: JavaScript
+ ace_mode: json
+ codemirror_mode: javascript
+ codemirror_mime_type: application/json
+ searchable: false
+ extensions:
+ - ".json"
+ - ".avsc"
+ - ".geojson"
+ - ".gltf"
+ - ".JSON-tmLanguage"
+ - ".jsonl"
+ - ".tfstate"
+ - ".tfstate.backup"
+ - ".topojson"
+ - ".webapp"
+ - ".webmanifest"
+ filenames:
+ - ".arcconfig"
+ - ".htmlhintrc"
+ - ".tern-config"
+ - ".tern-project"
+ - composer.lock
+ - mcmod.info
+ language_id: 174
+JSON with Comments:
+ type: data
+ group: JSON
+ tm_scope: source.js
+ ace_mode: javascript
+ codemirror_mode: javascript
+ codemirror_mime_type: text/javascript
+ aliases:
+ - jsonc
+ extensions:
+ - ".sublime-build"
+ - ".sublime-commands"
+ - ".sublime-completions"
+ - ".sublime-keymap"
+ - ".sublime-macro"
+ - ".sublime-menu"
+ - ".sublime-mousemap"
+ - ".sublime-project"
+ - ".sublime-settings"
+ - ".sublime-theme"
+ - ".sublime-workspace"
+ - ".sublime_metrics"
+ - ".sublime_session"
+ filenames:
+ - ".babelrc"
+ - ".eslintrc.json"
+ - ".jscsrc"
+ - ".jshintrc"
+ - ".jslintrc"
+ - tsconfig.json
+ language_id: 423
+JSON5:
+ type: data
+ extensions:
+ - ".json5"
+ tm_scope: source.js
+ ace_mode: javascript
+ codemirror_mode: javascript
+ codemirror_mime_type: application/json
+ language_id: 175
+JSONLD:
+ type: data
+ group: JavaScript
+ ace_mode: javascript
+ extensions:
+ - ".jsonld"
+ tm_scope: source.js
+ language_id: 176
+JSONiq:
+ color: "#40d47e"
+ type: programming
+ ace_mode: jsoniq
+ codemirror_mode: javascript
+ codemirror_mime_type: application/json
+ extensions:
+ - ".jq"
+ tm_scope: source.jq
+ language_id: 177
+JSX:
+ type: programming
+ group: JavaScript
+ extensions:
+ - ".jsx"
+ tm_scope: source.js.jsx
+ ace_mode: javascript
+ codemirror_mode: jsx
+ codemirror_mime_type: text/jsx
+ language_id: 178
+Jasmin:
+ type: programming
+ ace_mode: java
+ extensions:
+ - ".j"
+ tm_scope: source.jasmin
+ language_id: 180
+Java:
+ type: programming
+ ace_mode: java
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-java
+ color: "#b07219"
+ extensions:
+ - ".java"
+ language_id: 181
+Java Server Pages:
+ type: programming
+ group: Java
+ aliases:
+ - jsp
+ extensions:
+ - ".jsp"
+ tm_scope: text.html.jsp
+ ace_mode: jsp
+ codemirror_mode: htmlembedded
+ codemirror_mime_type: application/x-jsp
+ language_id: 182
+JavaScript:
+ type: programming
+ tm_scope: source.js
+ ace_mode: javascript
+ codemirror_mode: javascript
+ codemirror_mime_type: text/javascript
+ color: "#f1e05a"
+ aliases:
+ - js
+ - node
+ extensions:
+ - ".js"
+ - "._js"
+ - ".bones"
+ - ".es"
+ - ".es6"
+ - ".frag"
+ - ".gs"
+ - ".jake"
+ - ".jsb"
+ - ".jscad"
+ - ".jsfl"
+ - ".jsm"
+ - ".jss"
+ - ".mjs"
+ - ".njs"
+ - ".pac"
+ - ".sjs"
+ - ".ssjs"
+ - ".xsjs"
+ - ".xsjslib"
+ filenames:
+ - Jakefile
+ interpreters:
+ - node
+ language_id: 183
+Jison:
+ type: programming
+ group: Yacc
+ extensions:
+ - ".jison"
+ tm_scope: source.jison
+ ace_mode: text
+ language_id: 284531423
+Jison Lex:
+ type: programming
+ group: Lex
+ extensions:
+ - ".jisonlex"
+ tm_scope: source.jisonlex
+ ace_mode: text
+ language_id: 406395330
+Jolie:
+ type: programming
+ extensions:
+ - ".ol"
+ - ".iol"
+ interpreters:
+ - jolie
+ color: "#843179"
+ ace_mode: text
+ tm_scope: source.jolie
+ language_id: 998078858
+Julia:
+ type: programming
+ extensions:
+ - ".jl"
+ interpreters:
+ - julia
+ color: "#a270ba"
+ ace_mode: julia
+ codemirror_mode: julia
+ codemirror_mime_type: text/x-julia
+ language_id: 184
+Jupyter Notebook:
+ type: markup
+ ace_mode: json
+ codemirror_mode: javascript
+ codemirror_mime_type: application/json
+ tm_scope: source.json
+ color: "#DA5B0B"
+ extensions:
+ - ".ipynb"
+ filenames:
+ - Notebook
+ aliases:
+ - IPython Notebook
+ language_id: 185
+KRL:
+ type: programming
+ color: "#28430A"
+ extensions:
+ - ".krl"
+ tm_scope: none
+ ace_mode: text
+ language_id: 186
+KiCad Layout:
+ type: data
+ aliases:
+ - pcbnew
+ extensions:
+ - ".kicad_pcb"
+ - ".kicad_mod"
+ - ".kicad_wks"
+ filenames:
+ - fp-lib-table
+ tm_scope: source.pcb.sexp
+ ace_mode: lisp
+ codemirror_mode: commonlisp
+ codemirror_mime_type: text/x-common-lisp
+ language_id: 187
+KiCad Legacy Layout:
+ type: data
+ extensions:
+ - ".brd"
+ tm_scope: source.pcb.board
+ ace_mode: text
+ language_id: 140848857
+KiCad Schematic:
+ type: data
+ aliases:
+ - eeschema schematic
+ extensions:
+ - ".sch"
+ tm_scope: source.pcb.schematic
+ ace_mode: text
+ language_id: 622447435
+Kit:
+ type: markup
+ ace_mode: html
+ codemirror_mode: htmlmixed
+ codemirror_mime_type: text/html
+ extensions:
+ - ".kit"
+ tm_scope: text.html.basic
+ language_id: 188
+Kotlin:
+ type: programming
+ color: "#F18E33"
+ extensions:
+ - ".kt"
+ - ".ktm"
+ - ".kts"
+ tm_scope: source.kotlin
+ ace_mode: text
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-kotlin
+ language_id: 189
+LFE:
+ type: programming
+ color: "#4C3023"
+ extensions:
+ - ".lfe"
+ tm_scope: source.lisp
+ ace_mode: lisp
+ codemirror_mode: commonlisp
+ codemirror_mime_type: text/x-common-lisp
+ language_id: 190
+LLVM:
+ type: programming
+ extensions:
+ - ".ll"
+ ace_mode: text
+ color: "#185619"
+ language_id: 191
+LOLCODE:
+ type: programming
+ extensions:
+ - ".lol"
+ color: "#cc9900"
+ tm_scope: none
+ ace_mode: text
+ language_id: 192
+LSL:
+ type: programming
+ ace_mode: lsl
+ extensions:
+ - ".lsl"
+ - ".lslp"
+ interpreters:
+ - lsl
+ color: "#3d9970"
+ language_id: 193
+LabVIEW:
+ type: programming
+ extensions:
+ - ".lvproj"
+ tm_scope: text.xml
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ language_id: 194
+Lasso:
+ type: programming
+ color: "#999999"
+ extensions:
+ - ".lasso"
+ - ".las"
+ - ".lasso8"
+ - ".lasso9"
+ - ".ldml"
+ tm_scope: file.lasso
+ aliases:
+ - lassoscript
+ ace_mode: text
+ language_id: 195
+Latte:
+ type: markup
+ group: HTML
+ extensions:
+ - ".latte"
+ tm_scope: text.html.smarty
+ ace_mode: smarty
+ codemirror_mode: smarty
+ codemirror_mime_type: text/x-smarty
+ language_id: 196
+Lean:
+ type: programming
+ extensions:
+ - ".lean"
+ - ".hlean"
+ ace_mode: text
+ language_id: 197
+Less:
+ type: markup
+ group: CSS
+ extensions:
+ - ".less"
+ tm_scope: source.css.less
+ ace_mode: less
+ codemirror_mode: css
+ codemirror_mime_type: text/css
+ language_id: 198
+Lex:
+ type: programming
+ color: "#DBCA00"
+ aliases:
+ - flex
+ extensions:
+ - ".l"
+ - ".lex"
+ tm_scope: none
+ ace_mode: text
+ language_id: 199
+LilyPond:
+ type: programming
+ extensions:
+ - ".ly"
+ - ".ily"
+ ace_mode: text
+ language_id: 200
+Limbo:
+ type: programming
+ extensions:
+ - ".b"
+ - ".m"
+ tm_scope: none
+ ace_mode: text
+ language_id: 201
+Linker Script:
+ type: data
+ extensions:
+ - ".ld"
+ - ".lds"
+ - ".x"
+ filenames:
+ - ld.script
+ tm_scope: none
+ ace_mode: text
+ language_id: 202
+Linux Kernel Module:
+ type: data
+ extensions:
+ - ".mod"
+ tm_scope: none
+ ace_mode: text
+ language_id: 203
+Liquid:
+ type: markup
+ extensions:
+ - ".liquid"
+ tm_scope: text.html.liquid
+ ace_mode: liquid
+ language_id: 204
+Literate Agda:
+ type: programming
+ group: Agda
+ extensions:
+ - ".lagda"
+ tm_scope: none
+ ace_mode: text
+ language_id: 205
+Literate CoffeeScript:
+ type: programming
+ tm_scope: source.litcoffee
+ group: CoffeeScript
+ ace_mode: text
+ wrap: true
+ aliases:
+ - litcoffee
+ extensions:
+ - ".litcoffee"
+ language_id: 206
+Literate Haskell:
+ type: programming
+ group: Haskell
+ aliases:
+ - lhaskell
+ - lhs
+ extensions:
+ - ".lhs"
+ tm_scope: text.tex.latex.haskell
+ ace_mode: text
+ codemirror_mode: haskell-literate
+ codemirror_mime_type: text/x-literate-haskell
+ language_id: 207
+LiveScript:
+ type: programming
+ color: "#499886"
+ aliases:
+ - live-script
+ - ls
+ extensions:
+ - ".ls"
+ - "._ls"
+ filenames:
+ - Slakefile
+ ace_mode: livescript
+ codemirror_mode: livescript
+ codemirror_mime_type: text/x-livescript
+ language_id: 208
+Logos:
+ type: programming
+ extensions:
+ - ".xm"
+ - ".x"
+ - ".xi"
+ ace_mode: text
+ tm_scope: source.logos
+ language_id: 209
+Logtalk:
+ type: programming
+ extensions:
+ - ".lgt"
+ - ".logtalk"
+ ace_mode: text
+ language_id: 210
+LookML:
+ type: programming
+ ace_mode: yaml
+ codemirror_mode: yaml
+ codemirror_mime_type: text/x-yaml
+ color: "#652B81"
+ extensions:
+ - ".lookml"
+ - ".model.lkml"
+ - ".view.lkml"
+ tm_scope: source.yaml
+ language_id: 211
+LoomScript:
+ type: programming
+ extensions:
+ - ".ls"
+ tm_scope: source.loomscript
+ ace_mode: text
+ language_id: 212
+Lua:
+ type: programming
+ ace_mode: lua
+ codemirror_mode: lua
+ codemirror_mime_type: text/x-lua
+ color: "#000080"
+ extensions:
+ - ".lua"
+ - ".fcgi"
+ - ".nse"
+ - ".p8"
+ - ".pd_lua"
+ - ".rbxs"
+ - ".wlua"
+ interpreters:
+ - lua
+ language_id: 213
+M:
+ type: programming
+ aliases:
+ - mumps
+ extensions:
+ - ".mumps"
+ - ".m"
+ ace_mode: text
+ codemirror_mode: mumps
+ codemirror_mime_type: text/x-mumps
+ language_id: 214
+ tm_scope: none
+M4:
+ type: programming
+ extensions:
+ - ".m4"
+ tm_scope: none
+ ace_mode: text
+ language_id: 215
+M4Sugar:
+ type: programming
+ group: M4
+ aliases:
+ - autoconf
+ extensions:
+ - ".m4"
+ filenames:
+ - configure.ac
+ tm_scope: none
+ ace_mode: text
+ language_id: 216
+MAXScript:
+ type: programming
+ color: "#00a6a6"
+ extensions:
+ - ".ms"
+ - ".mcr"
+ tm_scope: source.maxscript
+ ace_mode: text
+ language_id: 217
+MQL4:
+ type: programming
+ color: "#62A8D6"
+ extensions:
+ - ".mq4"
+ - ".mqh"
+ tm_scope: source.mql5
+ ace_mode: c_cpp
+ language_id: 426
+MQL5:
+ type: programming
+ color: "#4A76B8"
+ extensions:
+ - ".mq5"
+ - ".mqh"
+ tm_scope: source.mql5
+ ace_mode: c_cpp
+ language_id: 427
+MTML:
+ type: markup
+ color: "#b7e1f4"
+ extensions:
+ - ".mtml"
+ tm_scope: text.html.basic
+ ace_mode: html
+ codemirror_mode: htmlmixed
+ codemirror_mime_type: text/html
+ language_id: 218
+MUF:
+ type: programming
+ group: Forth
+ extensions:
+ - ".muf"
+ - ".m"
+ tm_scope: none
+ ace_mode: forth
+ codemirror_mode: forth
+ codemirror_mime_type: text/x-forth
+ language_id: 219
+Makefile:
+ type: programming
+ color: "#427819"
+ aliases:
+ - bsdmake
+ - make
+ - mf
+ extensions:
+ - ".mak"
+ - ".d"
+ - ".make"
+ - ".mk"
+ - ".mkfile"
+ filenames:
+ - BSDmakefile
+ - GNUmakefile
+ - Kbuild
+ - Makefile
+ - Makefile.am
+ - Makefile.boot
+ - Makefile.frag
+ - Makefile.in
+ - Makefile.inc
+ - Makefile.wat
+ - makefile
+ - makefile.sco
+ - mkfile
+ interpreters:
+ - make
+ ace_mode: makefile
+ codemirror_mode: cmake
+ codemirror_mime_type: text/x-cmake
+ language_id: 220
+Mako:
+ type: programming
+ extensions:
+ - ".mako"
+ - ".mao"
+ tm_scope: text.html.mako
+ ace_mode: text
+ language_id: 221
+Markdown:
+ type: prose
+ aliases:
+ - pandoc
+ ace_mode: markdown
+ codemirror_mode: gfm
+ codemirror_mime_type: text/x-gfm
+ wrap: true
+ extensions:
+ - ".md"
+ - ".markdown"
+ - ".mdown"
+ - ".mdwn"
+ - ".mkd"
+ - ".mkdn"
+ - ".mkdown"
+ - ".ronn"
+ - ".workbook"
+ tm_scope: source.gfm
+ language_id: 222
+Marko:
+ group: HTML
+ type: markup
+ tm_scope: text.marko
+ extensions:
+ - ".marko"
+ aliases:
+ - markojs
+ ace_mode: text
+ codemirror_mode: htmlmixed
+ codemirror_mime_type: text/html
+ language_id: 932782397
+Mask:
+ type: markup
+ color: "#f97732"
+ ace_mode: mask
+ extensions:
+ - ".mask"
+ tm_scope: source.mask
+ language_id: 223
+Mathematica:
+ type: programming
+ extensions:
+ - ".mathematica"
+ - ".cdf"
+ - ".m"
+ - ".ma"
+ - ".mt"
+ - ".nb"
+ - ".nbp"
+ - ".wl"
+ - ".wlt"
+ aliases:
+ - mma
+ ace_mode: text
+ codemirror_mode: mathematica
+ codemirror_mime_type: text/x-mathematica
+ language_id: 224
+Matlab:
+ type: programming
+ color: "#e16737"
+ aliases:
+ - octave
+ extensions:
+ - ".matlab"
+ - ".m"
+ ace_mode: matlab
+ codemirror_mode: octave
+ codemirror_mime_type: text/x-octave
+ language_id: 225
+Maven POM:
+ type: data
+ tm_scope: text.xml.pom
+ filenames:
+ - pom.xml
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ language_id: 226
+Max:
+ type: programming
+ color: "#c4a79c"
+ aliases:
+ - max/msp
+ - maxmsp
+ extensions:
+ - ".maxpat"
+ - ".maxhelp"
+ - ".maxproj"
+ - ".mxt"
+ - ".pat"
+ tm_scope: source.json
+ ace_mode: json
+ codemirror_mode: javascript
+ codemirror_mime_type: application/json
+ language_id: 227
+MediaWiki:
+ type: prose
+ wrap: true
+ extensions:
+ - ".mediawiki"
+ - ".wiki"
+ tm_scope: text.html.mediawiki
+ ace_mode: text
+ language_id: 228
+Mercury:
+ type: programming
+ color: "#ff2b2b"
+ ace_mode: prolog
+ interpreters:
+ - mmi
+ extensions:
+ - ".m"
+ - ".moo"
+ tm_scope: source.mercury
+ language_id: 229
+Meson:
+ type: programming
+ color: "#007800"
+ filenames:
+ - meson.build
+ - meson_options.txt
+ tm_scope: source.meson
+ ace_mode: text
+ language_id: 799141244
+Metal:
+ type: programming
+ color: "#8f14e9"
+ extensions:
+ - ".metal"
+ tm_scope: source.c++
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-c++src
+ language_id: 230
+MiniD:
+ type: programming
+ searchable: false
+ extensions:
+ - ".minid"
+ tm_scope: none
+ ace_mode: text
+ language_id: 231
+Mirah:
+ type: programming
+ color: "#c7a938"
+ extensions:
+ - ".druby"
+ - ".duby"
+ - ".mirah"
+ tm_scope: source.ruby
+ ace_mode: ruby
+ codemirror_mode: ruby
+ codemirror_mime_type: text/x-ruby
+ language_id: 232
+Modelica:
+ type: programming
+ extensions:
+ - ".mo"
+ tm_scope: source.modelica
+ ace_mode: text
+ codemirror_mode: modelica
+ codemirror_mime_type: text/x-modelica
+ language_id: 233
+Modula-2:
+ type: programming
+ extensions:
+ - ".mod"
+ tm_scope: source.modula2
+ ace_mode: text
+ language_id: 234
+Modula-3:
+ type: programming
+ extensions:
+ - ".i3"
+ - ".ig"
+ - ".m3"
+ - ".mg"
+ color: "#223388"
+ ace_mode: text
+ tm_scope: source.modula-3
+ language_id: 564743864
+Module Management System:
+ type: programming
+ extensions:
+ - ".mms"
+ - ".mmk"
+ filenames:
+ - descrip.mmk
+ - descrip.mms
+ tm_scope: none
+ ace_mode: text
+ language_id: 235
+Monkey:
+ type: programming
+ extensions:
+ - ".monkey"
+ - ".monkey2"
+ ace_mode: text
+ tm_scope: source.monkey
+ language_id: 236
+Moocode:
+ type: programming
+ extensions:
+ - ".moo"
+ tm_scope: none
+ ace_mode: text
+ language_id: 237
+MoonScript:
+ type: programming
+ extensions:
+ - ".moon"
+ interpreters:
+ - moon
+ ace_mode: text
+ language_id: 238
+Myghty:
+ type: programming
+ extensions:
+ - ".myt"
+ tm_scope: none
+ ace_mode: text
+ language_id: 239
+NCL:
+ type: programming
+ color: "#28431f"
+ extensions:
+ - ".ncl"
+ tm_scope: source.ncl
+ ace_mode: text
+ language_id: 240
+NL:
+ type: data
+ extensions:
+ - ".nl"
+ tm_scope: none
+ ace_mode: text
+ language_id: 241
+NSIS:
+ type: programming
+ extensions:
+ - ".nsi"
+ - ".nsh"
+ ace_mode: text
+ codemirror_mode: nsis
+ codemirror_mime_type: text/x-nsis
+ language_id: 242
+Nearley:
+ type: programming
+ ace_mode: text
+ color: "#990000"
+ extensions:
+ - ".ne"
+ - ".nearley"
+ tm_scope: source.ne
+ language_id: 521429430
+Nemerle:
+ type: programming
+ color: "#3d3c6e"
+ extensions:
+ - ".n"
+ ace_mode: text
+ language_id: 243
+NetLinx:
+ type: programming
+ color: "#0aa0ff"
+ extensions:
+ - ".axs"
+ - ".axi"
+ tm_scope: source.netlinx
+ ace_mode: text
+ language_id: 244
+NetLinx+ERB:
+ type: programming
+ color: "#747faa"
+ extensions:
+ - ".axs.erb"
+ - ".axi.erb"
+ tm_scope: source.netlinx.erb
+ ace_mode: text
+ language_id: 245
+NetLogo:
+ type: programming
+ color: "#ff6375"
+ extensions:
+ - ".nlogo"
+ tm_scope: source.lisp
+ ace_mode: lisp
+ codemirror_mode: commonlisp
+ codemirror_mime_type: text/x-common-lisp
+ language_id: 246
+NewLisp:
+ type: programming
+ color: "#87AED7"
+ extensions:
+ - ".nl"
+ - ".lisp"
+ - ".lsp"
+ interpreters:
+ - newlisp
+ tm_scope: source.lisp
+ ace_mode: lisp
+ codemirror_mode: commonlisp
+ codemirror_mime_type: text/x-common-lisp
+ language_id: 247
+Nextflow:
+ type: programming
+ ace_mode: groovy
+ tm_scope: source.nextflow
+ color: "#3ac486"
+ extensions:
+ - ".nf"
+ filenames:
+ - nextflow.config
+ interpreters:
+ - nextflow
+ language_id: 506780613
+Nginx:
+ type: data
+ extensions:
+ - ".nginxconf"
+ - ".vhost"
+ filenames:
+ - nginx.conf
+ tm_scope: source.nginx
+ aliases:
+ - nginx configuration file
+ ace_mode: text
+ codemirror_mode: nginx
+ codemirror_mime_type: text/x-nginx-conf
+ language_id: 248
+Nim:
+ type: programming
+ color: "#37775b"
+ extensions:
+ - ".nim"
+ - ".nimrod"
+ ace_mode: text
+ tm_scope: source.nim
+ language_id: 249
+Ninja:
+ type: data
+ tm_scope: source.ninja
+ extensions:
+ - ".ninja"
+ ace_mode: text
+ language_id: 250
+Nit:
+ type: programming
+ color: "#009917"
+ extensions:
+ - ".nit"
+ tm_scope: source.nit
+ ace_mode: text
+ language_id: 251
+Nix:
+ type: programming
+ color: "#7e7eff"
+ extensions:
+ - ".nix"
+ aliases:
+ - nixos
+ tm_scope: source.nix
+ ace_mode: nix
+ language_id: 252
+Nu:
+ type: programming
+ color: "#c9df40"
+ aliases:
+ - nush
+ extensions:
+ - ".nu"
+ filenames:
+ - Nukefile
+ tm_scope: source.nu
+ ace_mode: scheme
+ codemirror_mode: scheme
+ codemirror_mime_type: text/x-scheme
+ interpreters:
+ - nush
+ language_id: 253
+NumPy:
+ type: programming
+ group: Python
+ extensions:
+ - ".numpy"
+ - ".numpyw"
+ - ".numsc"
+ tm_scope: none
+ ace_mode: text
+ codemirror_mode: python
+ codemirror_mime_type: text/x-python
+ language_id: 254
+OCaml:
+ type: programming
+ ace_mode: ocaml
+ codemirror_mode: mllike
+ codemirror_mime_type: text/x-ocaml
+ color: "#3be133"
+ extensions:
+ - ".ml"
+ - ".eliom"
+ - ".eliomi"
+ - ".ml4"
+ - ".mli"
+ - ".mll"
+ - ".mly"
+ interpreters:
+ - ocaml
+ - ocamlrun
+ - ocamlscript
+ tm_scope: source.ocaml
+ language_id: 255
+ObjDump:
+ type: data
+ extensions:
+ - ".objdump"
+ tm_scope: objdump.x86asm
+ ace_mode: assembly_x86
+ language_id: 256
+Objective-C:
+ type: programming
+ tm_scope: source.objc
+ color: "#438eff"
+ aliases:
+ - obj-c
+ - objc
+ - objectivec
+ extensions:
+ - ".m"
+ - ".h"
+ ace_mode: objectivec
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-objectivec
+ language_id: 257
+Objective-C++:
+ type: programming
+ tm_scope: source.objc++
+ color: "#6866fb"
+ aliases:
+ - obj-c++
+ - objc++
+ - objectivec++
+ extensions:
+ - ".mm"
+ ace_mode: objectivec
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-objectivec
+ language_id: 258
+Objective-J:
+ type: programming
+ color: "#ff0c5a"
+ aliases:
+ - obj-j
+ - objectivej
+ - objj
+ extensions:
+ - ".j"
+ - ".sj"
+ tm_scope: source.js.objj
+ ace_mode: text
+ language_id: 259
+Omgrofl:
+ type: programming
+ extensions:
+ - ".omgrofl"
+ color: "#cabbff"
+ tm_scope: none
+ ace_mode: text
+ language_id: 260
+Opa:
+ type: programming
+ extensions:
+ - ".opa"
+ ace_mode: text
+ language_id: 261
+Opal:
+ type: programming
+ color: "#f7ede0"
+ extensions:
+ - ".opal"
+ tm_scope: source.opal
+ ace_mode: text
+ language_id: 262
+OpenCL:
+ type: programming
+ group: C
+ extensions:
+ - ".cl"
+ - ".opencl"
+ tm_scope: source.c
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csrc
+ language_id: 263
+OpenEdge ABL:
+ type: programming
+ aliases:
+ - progress
+ - openedge
+ - abl
+ extensions:
+ - ".p"
+ - ".cls"
+ - ".w"
+ tm_scope: source.abl
+ ace_mode: text
+ language_id: 264
+OpenRC runscript:
+ type: programming
+ group: Shell
+ aliases:
+ - openrc
+ interpreters:
+ - openrc-run
+ tm_scope: source.shell
+ ace_mode: sh
+ codemirror_mode: shell
+ codemirror_mime_type: text/x-sh
+ language_id: 265
+OpenSCAD:
+ type: programming
+ extensions:
+ - ".scad"
+ tm_scope: source.scad
+ ace_mode: scad
+ language_id: 266
+OpenType Feature File:
+ type: data
+ aliases:
+ - AFDKO
+ extensions:
+ - ".fea"
+ tm_scope: source.opentype
+ ace_mode: text
+ language_id: 374317347
+Org:
+ type: prose
+ wrap: true
+ extensions:
+ - ".org"
+ tm_scope: none
+ ace_mode: text
+ language_id: 267
+Ox:
+ type: programming
+ extensions:
+ - ".ox"
+ - ".oxh"
+ - ".oxo"
+ tm_scope: source.ox
+ ace_mode: text
+ language_id: 268
+Oxygene:
+ type: programming
+ color: "#cdd0e3"
+ extensions:
+ - ".oxygene"
+ tm_scope: none
+ ace_mode: text
+ language_id: 269
+Oz:
+ type: programming
+ color: "#fab738"
+ extensions:
+ - ".oz"
+ tm_scope: source.oz
+ ace_mode: text
+ codemirror_mode: oz
+ codemirror_mime_type: text/x-oz
+ language_id: 270
+P4:
+ type: programming
+ color: "#7055b5"
+ extensions:
+ - ".p4"
+ tm_scope: source.p4
+ ace_mode: text
+ language_id: 348895984
+PAWN:
+ type: programming
+ color: "#dbb284"
+ extensions:
+ - ".pwn"
+ - ".inc"
+ tm_scope: source.pawn
+ ace_mode: text
+ language_id: 271
+PHP:
+ type: programming
+ tm_scope: text.html.php
+ ace_mode: php
+ codemirror_mode: php
+ codemirror_mime_type: application/x-httpd-php
+ color: "#4F5D95"
+ extensions:
+ - ".php"
+ - ".aw"
+ - ".ctp"
+ - ".fcgi"
+ - ".inc"
+ - ".php3"
+ - ".php4"
+ - ".php5"
+ - ".phps"
+ - ".phpt"
+ filenames:
+ - ".php"
+ - ".php_cs"
+ - ".php_cs.dist"
+ - Phakefile
+ interpreters:
+ - php
+ aliases:
+ - inc
+ language_id: 272
+PLSQL:
+ type: programming
+ ace_mode: sql
+ codemirror_mode: sql
+ codemirror_mime_type: text/x-plsql
+ tm_scope: none
+ color: "#dad8d8"
+ extensions:
+ - ".pls"
+ - ".bdy"
+ - ".ddl"
+ - ".fnc"
+ - ".pck"
+ - ".pkb"
+ - ".pks"
+ - ".plb"
+ - ".plsql"
+ - ".prc"
+ - ".spc"
+ - ".sql"
+ - ".tpb"
+ - ".tps"
+ - ".trg"
+ - ".vw"
+ language_id: 273
+PLpgSQL:
+ type: programming
+ ace_mode: pgsql
+ codemirror_mode: sql
+ codemirror_mime_type: text/x-sql
+ tm_scope: source.sql
+ extensions:
+ - ".sql"
+ language_id: 274
+POV-Ray SDL:
+ type: programming
+ aliases:
+ - pov-ray
+ - povray
+ extensions:
+ - ".pov"
+ - ".inc"
+ ace_mode: text
+ language_id: 275
+Pan:
+ type: programming
+ color: "#cc0000"
+ extensions:
+ - ".pan"
+ tm_scope: source.pan
+ ace_mode: text
+ language_id: 276
+Papyrus:
+ type: programming
+ color: "#6600cc"
+ extensions:
+ - ".psc"
+ tm_scope: source.papyrus.skyrim
+ ace_mode: text
+ language_id: 277
+Parrot:
+ type: programming
+ color: "#f3ca0a"
+ extensions:
+ - ".parrot"
+ tm_scope: none
+ ace_mode: text
+ language_id: 278
+Parrot Assembly:
+ group: Parrot
+ type: programming
+ aliases:
+ - pasm
+ extensions:
+ - ".pasm"
+ interpreters:
+ - parrot
+ tm_scope: none
+ ace_mode: text
+ language_id: 279
+Parrot Internal Representation:
+ group: Parrot
+ tm_scope: source.parrot.pir
+ type: programming
+ aliases:
+ - pir
+ extensions:
+ - ".pir"
+ interpreters:
+ - parrot
+ ace_mode: text
+ language_id: 280
+Pascal:
+ type: programming
+ color: "#E3F171"
+ extensions:
+ - ".pas"
+ - ".dfm"
+ - ".dpr"
+ - ".inc"
+ - ".lpr"
+ - ".pascal"
+ - ".pp"
+ interpreters:
+ - instantfpc
+ ace_mode: pascal
+ codemirror_mode: pascal
+ codemirror_mime_type: text/x-pascal
+ language_id: 281
+Pep8:
+ type: programming
+ color: "#C76F5B"
+ extensions:
+ - ".pep"
+ ace_mode: text
+ tm_scope: source.pep8
+ language_id: 840372442
+Perl:
+ type: programming
+ tm_scope: source.perl
+ ace_mode: perl
+ codemirror_mode: perl
+ codemirror_mime_type: text/x-perl
+ color: "#0298c3"
+ extensions:
+ - ".pl"
+ - ".al"
+ - ".cgi"
+ - ".fcgi"
+ - ".perl"
+ - ".ph"
+ - ".plx"
+ - ".pm"
+ - ".psgi"
+ - ".t"
+ filenames:
+ - Makefile.PL
+ - Rexfile
+ - ack
+ - cpanfile
+ interpreters:
+ - cperl
+ - perl
+ aliases:
+ - cperl
+ language_id: 282
+Perl 6:
+ type: programming
+ color: "#0000fb"
+ extensions:
+ - ".6pl"
+ - ".6pm"
+ - ".nqp"
+ - ".p6"
+ - ".p6l"
+ - ".p6m"
+ - ".pl"
+ - ".pl6"
+ - ".pm"
+ - ".pm6"
+ - ".t"
+ interpreters:
+ - perl6
+ aliases:
+ - perl6
+ tm_scope: source.perl6fe
+ ace_mode: perl
+ codemirror_mode: perl
+ codemirror_mime_type: text/x-perl
+ language_id: 283
+Pic:
+ type: markup
+ group: Roff
+ tm_scope: source.pic
+ extensions:
+ - ".pic"
+ - ".chem"
+ ace_mode: text
+ codemirror_mode: troff
+ codemirror_mime_type: text/troff
+ language_id: 425
+Pickle:
+ type: data
+ extensions:
+ - ".pkl"
+ tm_scope: none
+ ace_mode: text
+ language_id: 284
+PicoLisp:
+ type: programming
+ extensions:
+ - ".l"
+ interpreters:
+ - picolisp
+ - pil
+ tm_scope: source.lisp
+ ace_mode: lisp
+ language_id: 285
+PigLatin:
+ type: programming
+ color: "#fcd7de"
+ extensions:
+ - ".pig"
+ tm_scope: source.pig_latin
+ ace_mode: text
+ language_id: 286
+Pike:
+ type: programming
+ color: "#005390"
+ extensions:
+ - ".pike"
+ - ".pmod"
+ interpreters:
+ - pike
+ ace_mode: text
+ language_id: 287
+Pod:
+ type: prose
+ ace_mode: perl
+ codemirror_mode: perl
+ codemirror_mime_type: text/x-perl
+ wrap: true
+ extensions:
+ - ".pod"
+ interpreters:
+ - perl
+ tm_scope: none
+ language_id: 288
+PogoScript:
+ type: programming
+ color: "#d80074"
+ extensions:
+ - ".pogo"
+ tm_scope: source.pogoscript
+ ace_mode: text
+ language_id: 289
+Pony:
+ type: programming
+ extensions:
+ - ".pony"
+ tm_scope: source.pony
+ ace_mode: text
+ language_id: 290
+PostCSS:
+ type: markup
+ tm_scope: source.postcss
+ group: CSS
+ extensions:
+ - ".pcss"
+ ace_mode: text
+ language_id: 262764437
+PostScript:
+ type: markup
+ color: "#da291c"
+ extensions:
+ - ".ps"
+ - ".eps"
+ - ".pfa"
+ tm_scope: source.postscript
+ aliases:
+ - postscr
+ ace_mode: text
+ language_id: 291
+PowerBuilder:
+ type: programming
+ color: "#8f0f8d"
+ extensions:
+ - ".pbt"
+ - ".sra"
+ - ".sru"
+ - ".srw"
+ tm_scope: none
+ ace_mode: text
+ language_id: 292
+PowerShell:
+ type: programming
+ color: "#012456"
+ ace_mode: powershell
+ codemirror_mode: powershell
+ codemirror_mime_type: application/x-powershell
+ aliases:
+ - posh
+ - pwsh
+ extensions:
+ - ".ps1"
+ - ".psd1"
+ - ".psm1"
+ interpreters:
+ - pwsh
+ language_id: 293
+Processing:
+ type: programming
+ color: "#0096D8"
+ extensions:
+ - ".pde"
+ ace_mode: text
+ language_id: 294
+Prolog:
+ type: programming
+ color: "#74283c"
+ extensions:
+ - ".pl"
+ - ".pro"
+ - ".prolog"
+ - ".yap"
+ interpreters:
+ - swipl
+ - yap
+ tm_scope: source.prolog
+ ace_mode: prolog
+ language_id: 295
+Propeller Spin:
+ type: programming
+ color: "#7fa2a7"
+ extensions:
+ - ".spin"
+ tm_scope: source.spin
+ ace_mode: text
+ language_id: 296
+Protocol Buffer:
+ type: data
+ aliases:
+ - protobuf
+ - Protocol Buffers
+ extensions:
+ - ".proto"
+ tm_scope: source.protobuf
+ ace_mode: protobuf
+ codemirror_mode: protobuf
+ codemirror_mime_type: text/x-protobuf
+ language_id: 297
+Public Key:
+ type: data
+ extensions:
+ - ".asc"
+ - ".pub"
+ tm_scope: none
+ ace_mode: text
+ codemirror_mode: asciiarmor
+ codemirror_mime_type: application/pgp
+ language_id: 298
+Pug:
+ group: HTML
+ type: markup
+ extensions:
+ - ".jade"
+ - ".pug"
+ tm_scope: text.jade
+ ace_mode: jade
+ codemirror_mode: pug
+ codemirror_mime_type: text/x-pug
+ language_id: 179
+Puppet:
+ type: programming
+ color: "#302B6D"
+ extensions:
+ - ".pp"
+ filenames:
+ - Modulefile
+ ace_mode: text
+ codemirror_mode: puppet
+ codemirror_mime_type: text/x-puppet
+ tm_scope: source.puppet
+ language_id: 299
+Pure Data:
+ type: data
+ extensions:
+ - ".pd"
+ tm_scope: none
+ ace_mode: text
+ language_id: 300
+PureBasic:
+ type: programming
+ color: "#5a6986"
+ extensions:
+ - ".pb"
+ - ".pbi"
+ tm_scope: none
+ ace_mode: text
+ language_id: 301
+PureScript:
+ type: programming
+ color: "#1D222D"
+ extensions:
+ - ".purs"
+ tm_scope: source.purescript
+ ace_mode: haskell
+ codemirror_mode: haskell
+ codemirror_mime_type: text/x-haskell
+ language_id: 302
+Python:
+ type: programming
+ ace_mode: python
+ codemirror_mode: python
+ codemirror_mime_type: text/x-python
+ color: "#3572A5"
+ extensions:
+ - ".py"
+ - ".bzl"
+ - ".cgi"
+ - ".fcgi"
+ - ".gyp"
+ - ".gypi"
+ - ".lmi"
+ - ".py3"
+ - ".pyde"
+ - ".pyi"
+ - ".pyp"
+ - ".pyt"
+ - ".pyw"
+ - ".rpy"
+ - ".spec"
+ - ".tac"
+ - ".wsgi"
+ - ".xpy"
+ filenames:
+ - ".gclient"
+ - BUCK
+ - BUILD
+ - BUILD.bazel
+ - SConscript
+ - SConstruct
+ - Snakefile
+ - WORKSPACE
+ - wscript
+ interpreters:
+ - python
+ - python2
+ - python3
+ aliases:
+ - rusthon
+ - python3
+ language_id: 303
+Python console:
+ type: programming
+ group: Python
+ searchable: false
+ aliases:
+ - pycon
+ tm_scope: text.python.console
+ ace_mode: text
+ language_id: 428
+Python traceback:
+ type: data
+ group: Python
+ searchable: false
+ extensions:
+ - ".pytb"
+ tm_scope: text.python.traceback
+ ace_mode: text
+ language_id: 304
+QML:
+ type: programming
+ color: "#44a51c"
+ extensions:
+ - ".qml"
+ - ".qbs"
+ tm_scope: source.qml
+ ace_mode: text
+ language_id: 305
+QMake:
+ type: programming
+ extensions:
+ - ".pro"
+ - ".pri"
+ interpreters:
+ - qmake
+ ace_mode: text
+ language_id: 306
+Quake:
+ type: programming
+ filenames:
+ - m3makefile
+ - m3overrides
+ color: "#882233"
+ ace_mode: text
+ tm_scope: source.quake
+ language_id: 375265331
+R:
+ type: programming
+ color: "#198CE7"
+ aliases:
+ - R
+ - Rscript
+ - splus
+ extensions:
+ - ".r"
+ - ".rd"
+ - ".rsx"
+ filenames:
+ - ".Rprofile"
+ - expr-dist
+ interpreters:
+ - Rscript
+ ace_mode: r
+ codemirror_mode: r
+ codemirror_mime_type: text/x-rsrc
+ language_id: 307
+RAML:
+ type: markup
+ ace_mode: yaml
+ codemirror_mode: yaml
+ codemirror_mime_type: text/x-yaml
+ tm_scope: source.yaml
+ color: "#77d9fb"
+ extensions:
+ - ".raml"
+ language_id: 308
+RDoc:
+ type: prose
+ ace_mode: rdoc
+ wrap: true
+ extensions:
+ - ".rdoc"
+ tm_scope: text.rdoc
+ language_id: 309
+REALbasic:
+ type: programming
+ extensions:
+ - ".rbbas"
+ - ".rbfrm"
+ - ".rbmnu"
+ - ".rbres"
+ - ".rbtbar"
+ - ".rbuistate"
+ tm_scope: source.vbnet
+ ace_mode: text
+ language_id: 310
+REXX:
+ type: programming
+ aliases:
+ - arexx
+ extensions:
+ - ".rexx"
+ - ".pprx"
+ - ".rex"
+ interpreters:
+ - regina
+ - rexx
+ tm_scope: source.rexx
+ ace_mode: text
+ language_id: 311
+RHTML:
+ type: markup
+ group: HTML
+ extensions:
+ - ".rhtml"
+ tm_scope: text.html.erb
+ aliases:
+ - html+ruby
+ ace_mode: rhtml
+ codemirror_mode: htmlembedded
+ codemirror_mime_type: application/x-erb
+ language_id: 312
+RMarkdown:
+ type: prose
+ wrap: true
+ ace_mode: markdown
+ codemirror_mode: gfm
+ codemirror_mime_type: text/x-gfm
+ extensions:
+ - ".rmd"
+ tm_scope: source.gfm
+ language_id: 313
+RPC:
+ type: programming
+ aliases:
+ - rpcgen
+ - oncrpc
+ - xdr
+ ace_mode: c_cpp
+ extensions:
+ - ".x"
+ tm_scope: source.c
+ language_id: 1031374237
+RPM Spec:
+ type: data
+ tm_scope: source.rpm-spec
+ extensions:
+ - ".spec"
+ aliases:
+ - specfile
+ ace_mode: text
+ codemirror_mode: rpm
+ codemirror_mime_type: text/x-rpm-spec
+ language_id: 314
+RUNOFF:
+ type: markup
+ color: "#665a4e"
+ extensions:
+ - ".rnh"
+ - ".rno"
+ tm_scope: text.runoff
+ ace_mode: text
+ language_id: 315
+Racket:
+ type: programming
+ color: "#22228f"
+ extensions:
+ - ".rkt"
+ - ".rktd"
+ - ".rktl"
+ - ".scrbl"
+ interpreters:
+ - racket
+ tm_scope: source.racket
+ ace_mode: lisp
+ language_id: 316
+Ragel:
+ type: programming
+ color: "#9d5200"
+ extensions:
+ - ".rl"
+ aliases:
+ - ragel-rb
+ - ragel-ruby
+ tm_scope: none
+ ace_mode: text
+ language_id: 317
+Rascal:
+ type: programming
+ color: "#fffaa0"
+ extensions:
+ - ".rsc"
+ tm_scope: source.rascal
+ ace_mode: text
+ language_id: 173616037
+Raw token data:
+ type: data
+ aliases:
+ - raw
+ extensions:
+ - ".raw"
+ tm_scope: none
+ ace_mode: text
+ language_id: 318
+Reason:
+ type: programming
+ group: OCaml
+ ace_mode: rust
+ codemirror_mode: rust
+ codemirror_mime_type: text/x-rustsrc
+ extensions:
+ - ".re"
+ - ".rei"
+ interpreters:
+ - ocaml
+ tm_scope: source.reason
+ language_id: 869538413
+Rebol:
+ type: programming
+ color: "#358a5b"
+ extensions:
+ - ".reb"
+ - ".r"
+ - ".r2"
+ - ".r3"
+ - ".rebol"
+ ace_mode: text
+ tm_scope: source.rebol
+ language_id: 319
+Red:
+ type: programming
+ color: "#f50000"
+ extensions:
+ - ".red"
+ - ".reds"
+ aliases:
+ - red/system
+ tm_scope: source.red
+ ace_mode: text
+ language_id: 320
+Redcode:
+ type: programming
+ extensions:
+ - ".cw"
+ tm_scope: none
+ ace_mode: text
+ language_id: 321
+Regular Expression:
+ type: data
+ extensions:
+ - ".regexp"
+ - ".regex"
+ aliases:
+ - regexp
+ - regex
+ ace_mode: text
+ tm_scope: source.regexp
+ language_id: 363378884
+Ren'Py:
+ type: programming
+ aliases:
+ - renpy
+ color: "#ff7f7f"
+ extensions:
+ - ".rpy"
+ tm_scope: source.renpy
+ ace_mode: python
+ language_id: 322
+RenderScript:
+ type: programming
+ extensions:
+ - ".rs"
+ - ".rsh"
+ tm_scope: none
+ ace_mode: text
+ language_id: 323
+Ring:
+ type: programming
+ color: "#2D54CB"
+ extensions:
+ - ".ring"
+ tm_scope: source.ring
+ ace_mode: text
+ language_id: 431
+RobotFramework:
+ type: programming
+ extensions:
+ - ".robot"
+ tm_scope: text.robot
+ ace_mode: text
+ language_id: 324
+Roff:
+ type: markup
+ color: "#ecdebe"
+ extensions:
+ - ".man"
+ - ".1"
+ - ".1in"
+ - ".1m"
+ - ".1x"
+ - ".2"
+ - ".3"
+ - ".3in"
+ - ".3m"
+ - ".3qt"
+ - ".3x"
+ - ".4"
+ - ".5"
+ - ".6"
+ - ".7"
+ - ".8"
+ - ".9"
+ - ".l"
+ - ".me"
+ - ".ms"
+ - ".n"
+ - ".nr"
+ - ".rno"
+ - ".roff"
+ - ".tmac"
+ filenames:
+ - mmn
+ - mmt
+ tm_scope: text.roff
+ aliases:
+ - nroff
+ ace_mode: text
+ codemirror_mode: troff
+ codemirror_mime_type: text/troff
+ language_id: 141
+Rouge:
+ type: programming
+ ace_mode: clojure
+ codemirror_mode: clojure
+ codemirror_mime_type: text/x-clojure
+ color: "#cc0088"
+ extensions:
+ - ".rg"
+ tm_scope: source.clojure
+ language_id: 325
+Ruby:
+ type: programming
+ ace_mode: ruby
+ codemirror_mode: ruby
+ codemirror_mime_type: text/x-ruby
+ color: "#701516"
+ aliases:
+ - jruby
+ - macruby
+ - rake
+ - rb
+ - rbx
+ extensions:
+ - ".rb"
+ - ".builder"
+ - ".eye"
+ - ".fcgi"
+ - ".gemspec"
+ - ".god"
+ - ".jbuilder"
+ - ".mspec"
+ - ".pluginspec"
+ - ".podspec"
+ - ".rabl"
+ - ".rake"
+ - ".rbuild"
+ - ".rbw"
+ - ".rbx"
+ - ".ru"
+ - ".ruby"
+ - ".spec"
+ - ".thor"
+ - ".watchr"
+ interpreters:
+ - ruby
+ - macruby
+ - rake
+ - jruby
+ - rbx
+ filenames:
+ - ".irbrc"
+ - ".pryrc"
+ - Appraisals
+ - Berksfile
+ - Brewfile
+ - Buildfile
+ - Capfile
+ - Dangerfile
+ - Deliverfile
+ - Fastfile
+ - Gemfile
+ - Gemfile.lock
+ - Guardfile
+ - Jarfile
+ - Mavenfile
+ - Podfile
+ - Puppetfile
+ - Rakefile
+ - Snapfile
+ - Thorfile
+ - Vagrantfile
+ - buildfile
+ language_id: 326
+Rust:
+ type: programming
+ color: "#dea584"
+ extensions:
+ - ".rs"
+ - ".rs.in"
+ ace_mode: rust
+ codemirror_mode: rust
+ codemirror_mime_type: text/x-rustsrc
+ language_id: 327
+SAS:
+ type: programming
+ color: "#B34936"
+ extensions:
+ - ".sas"
+ tm_scope: source.sas
+ ace_mode: text
+ codemirror_mode: sas
+ codemirror_mime_type: text/x-sas
+ language_id: 328
+SCSS:
+ type: markup
+ tm_scope: source.scss
+ group: CSS
+ ace_mode: scss
+ codemirror_mode: css
+ codemirror_mime_type: text/x-scss
+ extensions:
+ - ".scss"
+ language_id: 329
+SMT:
+ type: programming
+ extensions:
+ - ".smt2"
+ - ".smt"
+ interpreters:
+ - boolector
+ - cvc4
+ - mathsat5
+ - opensmt
+ - smtinterpol
+ - smt-rat
+ - stp
+ - verit
+ - yices2
+ - z3
+ tm_scope: source.smt
+ ace_mode: text
+ language_id: 330
+SPARQL:
+ type: data
+ tm_scope: source.sparql
+ ace_mode: text
+ codemirror_mode: sparql
+ codemirror_mime_type: application/sparql-query
+ extensions:
+ - ".sparql"
+ - ".rq"
+ language_id: 331
+SQF:
+ type: programming
+ color: "#3F3F3F"
+ extensions:
+ - ".sqf"
+ - ".hqf"
+ tm_scope: source.sqf
+ ace_mode: text
+ language_id: 332
+SQL:
+ type: data
+ tm_scope: source.sql
+ ace_mode: sql
+ codemirror_mode: sql
+ codemirror_mime_type: text/x-sql
+ extensions:
+ - ".sql"
+ - ".cql"
+ - ".ddl"
+ - ".inc"
+ - ".mysql"
+ - ".prc"
+ - ".tab"
+ - ".udf"
+ - ".viw"
+ language_id: 333
+SQLPL:
+ type: programming
+ ace_mode: sql
+ codemirror_mode: sql
+ codemirror_mime_type: text/x-sql
+ tm_scope: source.sql
+ extensions:
+ - ".sql"
+ - ".db2"
+ language_id: 334
+SRecode Template:
+ type: markup
+ color: "#348a34"
+ tm_scope: source.lisp
+ ace_mode: lisp
+ codemirror_mode: commonlisp
+ codemirror_mime_type: text/x-common-lisp
+ extensions:
+ - ".srt"
+ language_id: 335
+STON:
+ type: data
+ group: Smalltalk
+ extensions:
+ - ".ston"
+ tm_scope: source.smalltalk
+ ace_mode: text
+ language_id: 336
+SVG:
+ type: data
+ extensions:
+ - ".svg"
+ tm_scope: text.xml
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ language_id: 337
+Sage:
+ type: programming
+ group: Python
+ extensions:
+ - ".sage"
+ - ".sagews"
+ tm_scope: source.python
+ ace_mode: python
+ codemirror_mode: python
+ codemirror_mime_type: text/x-python
+ language_id: 338
+SaltStack:
+ type: programming
+ color: "#646464"
+ aliases:
+ - saltstate
+ - salt
+ extensions:
+ - ".sls"
+ tm_scope: source.yaml.salt
+ ace_mode: yaml
+ codemirror_mode: yaml
+ codemirror_mime_type: text/x-yaml
+ language_id: 339
+Sass:
+ type: markup
+ tm_scope: source.sass
+ group: CSS
+ extensions:
+ - ".sass"
+ ace_mode: sass
+ codemirror_mode: sass
+ codemirror_mime_type: text/x-sass
+ language_id: 340
+Scala:
+ type: programming
+ ace_mode: scala
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-scala
+ color: "#c22d40"
+ extensions:
+ - ".scala"
+ - ".kojo"
+ - ".sbt"
+ - ".sc"
+ interpreters:
+ - scala
+ language_id: 341
+Scaml:
+ group: HTML
+ type: markup
+ extensions:
+ - ".scaml"
+ tm_scope: source.scaml
+ ace_mode: text
+ language_id: 342
+Scheme:
+ type: programming
+ color: "#1e4aec"
+ extensions:
+ - ".scm"
+ - ".sch"
+ - ".sld"
+ - ".sls"
+ - ".sps"
+ - ".ss"
+ interpreters:
+ - guile
+ - bigloo
+ - chicken
+ - csi
+ - gosh
+ - r6rs
+ ace_mode: scheme
+ codemirror_mode: scheme
+ codemirror_mime_type: text/x-scheme
+ language_id: 343
+Scilab:
+ type: programming
+ extensions:
+ - ".sci"
+ - ".sce"
+ - ".tst"
+ ace_mode: text
+ language_id: 344
+Self:
+ type: programming
+ color: "#0579aa"
+ extensions:
+ - ".self"
+ tm_scope: none
+ ace_mode: text
+ language_id: 345
+ShaderLab:
+ type: programming
+ extensions:
+ - ".shader"
+ ace_mode: text
+ tm_scope: source.shaderlab
+ language_id: 664257356
+Shell:
+ type: programming
+ color: "#89e051"
+ aliases:
+ - sh
+ - shell-script
+ - bash
+ - zsh
+ extensions:
+ - ".sh"
+ - ".bash"
+ - ".bats"
+ - ".cgi"
+ - ".command"
+ - ".fcgi"
+ - ".ksh"
+ - ".sh.in"
+ - ".tmux"
+ - ".tool"
+ - ".zsh"
+ filenames:
+ - ".bash_history"
+ - ".bash_logout"
+ - ".bash_profile"
+ - ".bashrc"
+ - ".cshrc"
+ - ".login"
+ - ".profile"
+ - ".zlogin"
+ - ".zlogout"
+ - ".zprofile"
+ - ".zshenv"
+ - ".zshrc"
+ - 9fs
+ - PKGBUILD
+ - bash_logout
+ - bash_profile
+ - bashrc
+ - cshrc
+ - gradlew
+ - login
+ - man
+ - profile
+ - zlogin
+ - zlogout
+ - zprofile
+ - zshenv
+ - zshrc
+ interpreters:
+ - ash
+ - bash
+ - dash
+ - ksh
+ - mksh
+ - pdksh
+ - rc
+ - sh
+ - zsh
+ ace_mode: sh
+ codemirror_mode: shell
+ codemirror_mime_type: text/x-sh
+ language_id: 346
+ShellSession:
+ type: programming
+ extensions:
+ - ".sh-session"
+ aliases:
+ - bash session
+ - console
+ tm_scope: text.shell-session
+ ace_mode: sh
+ codemirror_mode: shell
+ codemirror_mime_type: text/x-sh
+ language_id: 347
+Shen:
+ type: programming
+ color: "#120F14"
+ extensions:
+ - ".shen"
+ tm_scope: source.shen
+ ace_mode: text
+ language_id: 348
+Slash:
+ type: programming
+ color: "#007eff"
+ extensions:
+ - ".sl"
+ tm_scope: text.html.slash
+ ace_mode: text
+ language_id: 349
+Slim:
+ group: HTML
+ type: markup
+ extensions:
+ - ".slim"
+ tm_scope: text.slim
+ ace_mode: text
+ codemirror_mode: slim
+ codemirror_mime_type: text/x-slim
+ language_id: 350
+Smali:
+ type: programming
+ extensions:
+ - ".smali"
+ ace_mode: text
+ tm_scope: source.smali
+ language_id: 351
+Smalltalk:
+ type: programming
+ color: "#596706"
+ extensions:
+ - ".st"
+ - ".cs"
+ aliases:
+ - squeak
+ ace_mode: text
+ codemirror_mode: smalltalk
+ codemirror_mime_type: text/x-stsrc
+ language_id: 352
+Smarty:
+ type: programming
+ extensions:
+ - ".tpl"
+ ace_mode: smarty
+ codemirror_mode: smarty
+ codemirror_mime_type: text/x-smarty
+ tm_scope: text.html.smarty
+ language_id: 353
+Solidity:
+ type: programming
+ color: "#AA6746"
+ ace_mode: text
+ tm_scope: source.solidity
+ language_id: 237469032
+SourcePawn:
+ type: programming
+ color: "#5c7611"
+ aliases:
+ - sourcemod
+ extensions:
+ - ".sp"
+ - ".inc"
+ - ".sma"
+ tm_scope: source.sp
+ ace_mode: text
+ language_id: 354
+Spline Font Database:
+ type: data
+ extensions:
+ - ".sfd"
+ tm_scope: text.sfd
+ ace_mode: yaml
+ language_id: 767169629
+Squirrel:
+ type: programming
+ color: "#800000"
+ extensions:
+ - ".nut"
+ tm_scope: source.c++
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-c++src
+ language_id: 355
+Stan:
+ type: programming
+ color: "#b2011d"
+ extensions:
+ - ".stan"
+ ace_mode: text
+ tm_scope: source.stan
+ language_id: 356
+Standard ML:
+ type: programming
+ color: "#dc566d"
+ aliases:
+ - sml
+ extensions:
+ - ".ML"
+ - ".fun"
+ - ".sig"
+ - ".sml"
+ tm_scope: source.ml
+ ace_mode: text
+ codemirror_mode: mllike
+ codemirror_mime_type: text/x-ocaml
+ language_id: 357
+Stata:
+ type: programming
+ extensions:
+ - ".do"
+ - ".ado"
+ - ".doh"
+ - ".ihlp"
+ - ".mata"
+ - ".matah"
+ - ".sthlp"
+ ace_mode: text
+ language_id: 358
+Stylus:
+ type: markup
+ group: CSS
+ extensions:
+ - ".styl"
+ tm_scope: source.stylus
+ ace_mode: stylus
+ language_id: 359
+SubRip Text:
+ type: data
+ extensions:
+ - ".srt"
+ ace_mode: text
+ tm_scope: text.srt
+ language_id: 360
+SugarSS:
+ type: markup
+ tm_scope: source.css.postcss.sugarss
+ group: CSS
+ extensions:
+ - ".sss"
+ ace_mode: text
+ language_id: 826404698
+SuperCollider:
+ type: programming
+ color: "#46390b"
+ extensions:
+ - ".sc"
+ - ".scd"
+ interpreters:
+ - sclang
+ - scsynth
+ tm_scope: source.supercollider
+ ace_mode: text
+ language_id: 361
+Swift:
+ type: programming
+ color: "#ffac45"
+ extensions:
+ - ".swift"
+ ace_mode: text
+ codemirror_mode: swift
+ codemirror_mime_type: text/x-swift
+ language_id: 362
+SystemVerilog:
+ type: programming
+ color: "#DAE1C2"
+ extensions:
+ - ".sv"
+ - ".svh"
+ - ".vh"
+ ace_mode: verilog
+ codemirror_mode: verilog
+ codemirror_mime_type: text/x-systemverilog
+ language_id: 363
+TI Program:
+ type: programming
+ ace_mode: text
+ color: "#A0AA87"
+ extensions:
+ - ".8xp"
+ - ".8xk"
+ - ".8xk.txt"
+ - ".8xp.txt"
+ language_id: 422
+ tm_scope: none
+TLA:
+ type: programming
+ extensions:
+ - ".tla"
+ tm_scope: source.tla
+ ace_mode: text
+ language_id: 364
+TOML:
+ type: data
+ extensions:
+ - ".toml"
+ filenames:
+ - Cargo.lock
+ - Gopkg.lock
+ tm_scope: source.toml
+ ace_mode: toml
+ codemirror_mode: toml
+ codemirror_mime_type: text/x-toml
+ language_id: 365
+TXL:
+ type: programming
+ extensions:
+ - ".txl"
+ tm_scope: source.txl
+ ace_mode: text
+ language_id: 366
+Tcl:
+ type: programming
+ color: "#e4cc98"
+ extensions:
+ - ".tcl"
+ - ".adp"
+ - ".tm"
+ filenames:
+ - owh
+ - starfield
+ interpreters:
+ - tclsh
+ - wish
+ ace_mode: tcl
+ codemirror_mode: tcl
+ codemirror_mime_type: text/x-tcl
+ language_id: 367
+Tcsh:
+ type: programming
+ group: Shell
+ extensions:
+ - ".tcsh"
+ - ".csh"
+ tm_scope: source.shell
+ ace_mode: sh
+ codemirror_mode: shell
+ codemirror_mime_type: text/x-sh
+ language_id: 368
+TeX:
+ type: markup
+ color: "#3D6117"
+ ace_mode: tex
+ codemirror_mode: stex
+ codemirror_mime_type: text/x-stex
+ wrap: true
+ aliases:
+ - latex
+ extensions:
+ - ".tex"
+ - ".aux"
+ - ".bbx"
+ - ".bib"
+ - ".cbx"
+ - ".cls"
+ - ".dtx"
+ - ".ins"
+ - ".lbx"
+ - ".ltx"
+ - ".mkii"
+ - ".mkiv"
+ - ".mkvi"
+ - ".sty"
+ - ".toc"
+ language_id: 369
+Tea:
+ type: markup
+ extensions:
+ - ".tea"
+ tm_scope: source.tea
+ ace_mode: text
+ language_id: 370
+Terra:
+ type: programming
+ extensions:
+ - ".t"
+ color: "#00004c"
+ ace_mode: lua
+ codemirror_mode: lua
+ codemirror_mime_type: text/x-lua
+ interpreters:
+ - lua
+ language_id: 371
+Text:
+ type: prose
+ wrap: true
+ aliases:
+ - fundamental
+ extensions:
+ - ".txt"
+ - ".fr"
+ - ".nb"
+ - ".ncl"
+ - ".no"
+ filenames:
+ - COPYING
+ - COPYING.regex
+ - COPYRIGHT.regex
+ - FONTLOG
+ - INSTALL
+ - INSTALL.mysql
+ - LICENSE
+ - LICENSE.mysql
+ - NEWS
+ - README.1ST
+ - README.me
+ - README.mysql
+ - click.me
+ - delete.me
+ - keep.me
+ - read.me
+ - readme.1st
+ - test.me
+ tm_scope: none
+ ace_mode: text
+ language_id: 372
+Textile:
+ type: prose
+ ace_mode: textile
+ codemirror_mode: textile
+ codemirror_mime_type: text/x-textile
+ wrap: true
+ extensions:
+ - ".textile"
+ tm_scope: none
+ language_id: 373
+Thrift:
+ type: programming
+ tm_scope: source.thrift
+ extensions:
+ - ".thrift"
+ ace_mode: text
+ language_id: 374
+Turing:
+ type: programming
+ color: "#cf142b"
+ extensions:
+ - ".t"
+ - ".tu"
+ tm_scope: source.turing
+ ace_mode: text
+ language_id: 375
+Turtle:
+ type: data
+ extensions:
+ - ".ttl"
+ tm_scope: source.turtle
+ ace_mode: text
+ codemirror_mode: turtle
+ codemirror_mime_type: text/turtle
+ language_id: 376
+Twig:
+ type: markup
+ group: HTML
+ extensions:
+ - ".twig"
+ tm_scope: text.html.twig
+ ace_mode: twig
+ codemirror_mode: twig
+ codemirror_mime_type: text/x-twig
+ language_id: 377
+Type Language:
+ type: data
+ aliases:
+ - tl
+ extensions:
+ - ".tl"
+ tm_scope: source.tl
+ ace_mode: text
+ language_id: 632765617
+TypeScript:
+ type: programming
+ color: "#2b7489"
+ aliases:
+ - ts
+ extensions:
+ - ".ts"
+ - ".tsx"
+ tm_scope: source.ts
+ ace_mode: typescript
+ codemirror_mode: javascript
+ codemirror_mime_type: application/typescript
+ language_id: 378
+Unified Parallel C:
+ type: programming
+ group: C
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csrc
+ extensions:
+ - ".upc"
+ tm_scope: source.c
+ language_id: 379
+Unity3D Asset:
+ type: data
+ ace_mode: yaml
+ codemirror_mode: yaml
+ codemirror_mime_type: text/x-yaml
+ extensions:
+ - ".anim"
+ - ".asset"
+ - ".mat"
+ - ".meta"
+ - ".prefab"
+ - ".unity"
+ tm_scope: source.yaml
+ language_id: 380
+Unix Assembly:
+ type: programming
+ group: Assembly
+ extensions:
+ - ".s"
+ - ".ms"
+ tm_scope: source.x86
+ ace_mode: assembly_x86
+ language_id: 120
+Uno:
+ type: programming
+ extensions:
+ - ".uno"
+ ace_mode: csharp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csharp
+ tm_scope: source.cs
+ language_id: 381
+UnrealScript:
+ type: programming
+ color: "#a54c4d"
+ extensions:
+ - ".uc"
+ tm_scope: source.java
+ ace_mode: java
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-java
+ language_id: 382
+UrWeb:
+ type: programming
+ aliases:
+ - Ur/Web
+ - Ur
+ extensions:
+ - ".ur"
+ - ".urs"
+ tm_scope: source.ur
+ ace_mode: text
+ language_id: 383
+VCL:
+ type: programming
+ color: "#148AA8"
+ extensions:
+ - ".vcl"
+ tm_scope: source.varnish.vcl
+ ace_mode: text
+ language_id: 384
+VHDL:
+ type: programming
+ color: "#adb2cb"
+ extensions:
+ - ".vhdl"
+ - ".vhd"
+ - ".vhf"
+ - ".vhi"
+ - ".vho"
+ - ".vhs"
+ - ".vht"
+ - ".vhw"
+ ace_mode: vhdl
+ codemirror_mode: vhdl
+ codemirror_mime_type: text/x-vhdl
+ language_id: 385
+Vala:
+ type: programming
+ color: "#fbe5cd"
+ extensions:
+ - ".vala"
+ - ".vapi"
+ ace_mode: vala
+ language_id: 386
+Verilog:
+ type: programming
+ color: "#b2b7f8"
+ extensions:
+ - ".v"
+ - ".veo"
+ ace_mode: verilog
+ codemirror_mode: verilog
+ codemirror_mime_type: text/x-verilog
+ language_id: 387
+Vim script:
+ type: programming
+ color: "#199f4b"
+ tm_scope: source.viml
+ aliases:
+ - vim
+ - viml
+ - nvim
+ extensions:
+ - ".vim"
+ filenames:
+ - ".gvimrc"
+ - ".nvimrc"
+ - ".vimrc"
+ - _vimrc
+ - gvimrc
+ - nvimrc
+ - vimrc
+ ace_mode: text
+ language_id: 388
+Visual Basic:
+ type: programming
+ color: "#945db7"
+ extensions:
+ - ".vb"
+ - ".bas"
+ - ".cls"
+ - ".frm"
+ - ".frx"
+ - ".vba"
+ - ".vbhtml"
+ - ".vbs"
+ tm_scope: source.vbnet
+ aliases:
+ - vb.net
+ - vbnet
+ ace_mode: text
+ codemirror_mode: vb
+ codemirror_mime_type: text/x-vb
+ language_id: 389
+Volt:
+ type: programming
+ color: "#1F1F1F"
+ extensions:
+ - ".volt"
+ tm_scope: source.d
+ ace_mode: d
+ codemirror_mode: d
+ codemirror_mime_type: text/x-d
+ language_id: 390
+Vue:
+ type: markup
+ color: "#2c3e50"
+ extensions:
+ - ".vue"
+ tm_scope: text.html.vue
+ ace_mode: html
+ language_id: 391
+Wavefront Material:
+ type: data
+ extensions:
+ - ".mtl"
+ tm_scope: source.wavefront.mtl
+ ace_mode: text
+ language_id: 392
+Wavefront Object:
+ type: data
+ extensions:
+ - ".obj"
+ tm_scope: source.wavefront.obj
+ ace_mode: text
+ language_id: 393
+Web Ontology Language:
+ type: data
+ extensions:
+ - ".owl"
+ tm_scope: text.xml
+ ace_mode: xml
+ language_id: 394
+WebAssembly:
+ type: programming
+ color: "#04133b"
+ extensions:
+ - ".wast"
+ - ".wat"
+ aliases:
+ - wast
+ - wasm
+ tm_scope: source.webassembly
+ ace_mode: lisp
+ codemirror_mode: commonlisp
+ codemirror_mime_type: text/x-common-lisp
+ language_id: 956556503
+WebIDL:
+ type: programming
+ extensions:
+ - ".webidl"
+ tm_scope: source.webidl
+ ace_mode: text
+ codemirror_mode: webidl
+ codemirror_mime_type: text/x-webidl
+ language_id: 395
+World of Warcraft Addon Data:
+ type: data
+ extensions:
+ - ".toc"
+ tm_scope: source.toc
+ ace_mode: text
+ language_id: 396
+X BitMap:
+ type: data
+ group: C
+ aliases:
+ - xbm
+ extensions:
+ - ".xbm"
+ ace_mode: c_cpp
+ tm_scope: source.c
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csrc
+ language_id: 782911107
+X PixMap:
+ type: data
+ group: C
+ aliases:
+ - xpm
+ extensions:
+ - ".xpm"
+ - ".pm"
+ ace_mode: c_cpp
+ tm_scope: source.c
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csrc
+ language_id: 781846279
+X10:
+ type: programming
+ aliases:
+ - xten
+ ace_mode: text
+ extensions:
+ - ".x10"
+ color: "#4B6BEF"
+ tm_scope: source.x10
+ language_id: 397
+XC:
+ type: programming
+ color: "#99DA07"
+ extensions:
+ - ".xc"
+ tm_scope: source.xc
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csrc
+ language_id: 398
+XCompose:
+ type: data
+ filenames:
+ - ".XCompose"
+ - XCompose
+ - xcompose
+ tm_scope: config.xcompose
+ ace_mode: text
+ language_id: 225167241
+XML:
+ type: data
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ aliases:
+ - rss
+ - xsd
+ - wsdl
+ extensions:
+ - ".xml"
+ - ".adml"
+ - ".admx"
+ - ".ant"
+ - ".axml"
+ - ".builds"
+ - ".ccproj"
+ - ".ccxml"
+ - ".clixml"
+ - ".cproject"
+ - ".cscfg"
+ - ".csdef"
+ - ".csl"
+ - ".csproj"
+ - ".ct"
+ - ".depproj"
+ - ".dita"
+ - ".ditamap"
+ - ".ditaval"
+ - ".dll.config"
+ - ".dotsettings"
+ - ".filters"
+ - ".fsproj"
+ - ".fxml"
+ - ".glade"
+ - ".gml"
+ - ".grxml"
+ - ".iml"
+ - ".ivy"
+ - ".jelly"
+ - ".jsproj"
+ - ".kml"
+ - ".launch"
+ - ".mdpolicy"
+ - ".mjml"
+ - ".mm"
+ - ".mod"
+ - ".mxml"
+ - ".natvis"
+ - ".ncl"
+ - ".ndproj"
+ - ".nproj"
+ - ".nuspec"
+ - ".odd"
+ - ".osm"
+ - ".pkgproj"
+ - ".plist"
+ - ".pluginspec"
+ - ".proj"
+ - ".props"
+ - ".ps1xml"
+ - ".psc1"
+ - ".pt"
+ - ".rdf"
+ - ".resx"
+ - ".rss"
+ - ".sch"
+ - ".scxml"
+ - ".sfproj"
+ - ".shproj"
+ - ".srdf"
+ - ".storyboard"
+ - ".stTheme"
+ - ".sublime-snippet"
+ - ".targets"
+ - ".tmCommand"
+ - ".tml"
+ - ".tmLanguage"
+ - ".tmPreferences"
+ - ".tmSnippet"
+ - ".tmTheme"
+ - ".ts"
+ - ".tsx"
+ - ".ui"
+ - ".urdf"
+ - ".ux"
+ - ".vbproj"
+ - ".vcxproj"
+ - ".vsixmanifest"
+ - ".vssettings"
+ - ".vstemplate"
+ - ".vxml"
+ - ".wixproj"
+ - ".wsdl"
+ - ".wsf"
+ - ".wxi"
+ - ".wxl"
+ - ".wxs"
+ - ".x3d"
+ - ".xacro"
+ - ".xaml"
+ - ".xib"
+ - ".xlf"
+ - ".xliff"
+ - ".xmi"
+ - ".xml.dist"
+ - ".xproj"
+ - ".xsd"
+ - ".xspec"
+ - ".xul"
+ - ".zcml"
+ filenames:
+ - ".classpath"
+ - ".cproject"
+ - ".project"
+ - App.config
+ - NuGet.config
+ - Settings.StyleCop
+ - Web.Debug.config
+ - Web.Release.config
+ - Web.config
+ - packages.config
+ language_id: 399
+XPages:
+ type: data
+ extensions:
+ - ".xsp-config"
+ - ".xsp.metadata"
+ tm_scope: text.xml
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ language_id: 400
+XProc:
+ type: programming
+ extensions:
+ - ".xpl"
+ - ".xproc"
+ tm_scope: text.xml
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ language_id: 401
+XQuery:
+ type: programming
+ color: "#5232e7"
+ extensions:
+ - ".xquery"
+ - ".xq"
+ - ".xql"
+ - ".xqm"
+ - ".xqy"
+ ace_mode: xquery
+ codemirror_mode: xquery
+ codemirror_mime_type: application/xquery
+ tm_scope: source.xq
+ language_id: 402
+XS:
+ type: programming
+ extensions:
+ - ".xs"
+ tm_scope: source.c
+ ace_mode: c_cpp
+ codemirror_mode: clike
+ codemirror_mime_type: text/x-csrc
+ language_id: 403
+XSLT:
+ type: programming
+ aliases:
+ - xsl
+ extensions:
+ - ".xslt"
+ - ".xsl"
+ tm_scope: text.xml.xsl
+ ace_mode: xml
+ codemirror_mode: xml
+ codemirror_mime_type: text/xml
+ color: "#EB8CEB"
+ language_id: 404
+Xojo:
+ type: programming
+ extensions:
+ - ".xojo_code"
+ - ".xojo_menu"
+ - ".xojo_report"
+ - ".xojo_script"
+ - ".xojo_toolbar"
+ - ".xojo_window"
+ tm_scope: source.vbnet
+ ace_mode: text
+ language_id: 405
+Xtend:
+ type: programming
+ extensions:
+ - ".xtend"
+ ace_mode: text
+ language_id: 406
+YAML:
+ type: data
+ tm_scope: source.yaml
+ aliases:
+ - yml
+ extensions:
+ - ".yml"
+ - ".mir"
+ - ".reek"
+ - ".rviz"
+ - ".sublime-syntax"
+ - ".syntax"
+ - ".yaml"
+ - ".yaml-tmlanguage"
+ - ".yml.mysql"
+ filenames:
+ - ".clang-format"
+ - ".clang-tidy"
+ - ".gemrc"
+ - glide.lock
+ ace_mode: yaml
+ codemirror_mode: yaml
+ codemirror_mime_type: text/x-yaml
+ language_id: 407
+YANG:
+ type: data
+ extensions:
+ - ".yang"
+ tm_scope: source.yang
+ ace_mode: text
+ language_id: 408
+YARA:
+ type: data
+ ace_mode: text
+ extensions:
+ - ".yar"
+ - ".yara"
+ tm_scope: source.yara
+ language_id: 805122868
+Yacc:
+ type: programming
+ extensions:
+ - ".y"
+ - ".yacc"
+ - ".yy"
+ tm_scope: source.bison
+ ace_mode: text
+ color: "#4B6C4B"
+ language_id: 409
+Zephir:
+ type: programming
+ color: "#118f9e"
+ extensions:
+ - ".zep"
+ tm_scope: source.php.zephir
+ ace_mode: php
+ language_id: 410
+Zimpl:
+ type: programming
+ extensions:
+ - ".zimpl"
+ - ".zmpl"
+ - ".zpl"
+ tm_scope: none
+ ace_mode: text
+ language_id: 411
+desktop:
+ type: data
+ extensions:
+ - ".desktop"
+ - ".desktop.in"
+ tm_scope: source.desktop
+ ace_mode: text
+ language_id: 412
+eC:
+ type: programming
+ color: "#913960"
+ extensions:
+ - ".ec"
+ - ".eh"
+ tm_scope: source.c.ec
+ ace_mode: text
+ language_id: 413
+edn:
+ type: data
+ ace_mode: clojure
+ codemirror_mode: clojure
+ codemirror_mime_type: text/x-clojure
+ extensions:
+ - ".edn"
+ tm_scope: source.clojure
+ language_id: 414
+fish:
+ type: programming
+ group: Shell
+ interpreters:
+ - fish
+ extensions:
+ - ".fish"
+ tm_scope: source.fish
+ ace_mode: text
+ language_id: 415
+mupad:
+ type: programming
+ extensions:
+ - ".mu"
+ ace_mode: text
+ language_id: 416
+nesC:
+ type: programming
+ color: "#94B0C7"
+ extensions:
+ - ".nc"
+ ace_mode: text
+ tm_scope: source.nesc
+ language_id: 417
+ooc:
+ type: programming
+ color: "#b0b77e"
+ extensions:
+ - ".ooc"
+ ace_mode: text
+ language_id: 418
+q:
+ type: programming
+ extensions:
+ - ".q"
+ tm_scope: source.q
+ ace_mode: text
+ color: "#0040cd"
+ language_id: 970539067
+reStructuredText:
+ type: prose
+ wrap: true
+ aliases:
+ - rst
+ extensions:
+ - ".rst"
+ - ".rest"
+ - ".rest.txt"
+ - ".rst.txt"
+ ace_mode: text
+ codemirror_mode: rst
+ codemirror_mime_type: text/x-rst
+ language_id: 419
+sed:
+ type: programming
+ color: "#64b970"
+ extensions:
+ - ".sed"
+ interpreters:
+ - gsed
+ - minised
+ - sed
+ - ssed
+ ace_mode: text
+ tm_scope: source.sed
+ language_id: 847830017
+wdl:
+ type: programming
+ color: "#42f1f4"
+ extensions:
+ - ".wdl"
+ tm_scope: source.wdl
+ ace_mode: text
+ language_id: 374521672
+wisp:
+ type: programming
+ ace_mode: clojure
+ codemirror_mode: clojure
+ codemirror_mime_type: text/x-clojure
+ color: "#7582D1"
+ extensions:
+ - ".wisp"
+ tm_scope: source.clojure
+ language_id: 420
+xBase:
+ type: programming
+ color: "#403a40"
+ aliases:
+ - advpl
+ - clipper
+ - foxpro
+ extensions:
+ - ".prg"
+ - ".ch"
+ - ".prw"
+ tm_scope: source.harbour
+ ace_mode: text
+ language_id: 421
diff --git a/yarn.lock b/yarn.lock
index 25ea8d7557c..292c7128d18 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -616,10 +616,10 @@
lodash "^4.17.10"
to-fast-properties "^2.0.0"
-"@gitlab-org/gitlab-svgs@^1.23.0", "@gitlab-org/gitlab-svgs@^1.29.0":
- version "1.31.0"
- resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.31.0.tgz#495b074669f93af40e34f9978ce887773dea470a"
- integrity sha512-tJbf99XX/ddFkXCXxQr9a0GJD9rPVoW3qMbU14dkxwG4WBmPEoVg+e7sLvm9OWTD1uUqiVW3qWKp++SGhhcRlw==
+"@gitlab-org/gitlab-svgs@^1.23.0", "@gitlab-org/gitlab-svgs@^1.32.0":
+ version "1.32.0"
+ resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.32.0.tgz#a65ab7724fa7d55be8e5cc9b2dbe3f0757432fd3"
+ integrity sha512-L3o8dFUd2nSkVZBwh2hCJWzNzADJ3dTBZxamND8NLosZK9/ohNhccmsQOZGyMCUHaOzm4vifaaXkAXh04UtMKA==
"@gitlab-org/gitlab-ui@^1.8.0":
version "1.8.0"
@@ -630,6 +630,19 @@
bootstrap-vue "^2.0.0-rc.11"
vue "^2.5.16"
+"@gitlab/eslint-config@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/eslint-config/-/eslint-config-1.1.0.tgz#9757764b3a78b6bacfbcd9533331cb6345ffdd59"
+ integrity sha512-tTdHj8nmZbgl7ygzZYYgfETgvFX/+Z/xA5abqbmwcejpI4fmJUhQwERTHu7P+NwQ2ywzPCS6dO4LljlT/r1jBw==
+ dependencies:
+ babel-eslint "^10.0.1"
+ eslint-config-airbnb-base "^13.1.0"
+ eslint-config-prettier "^3.1.0"
+ eslint-plugin-filenames "^1.3.2"
+ eslint-plugin-import "^2.14.0"
+ eslint-plugin-promise "^4.0.1"
+ eslint-plugin-vue "^5.0.0-beta.3"
+
"@sindresorhus/is@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
@@ -1136,10 +1149,10 @@ babel-code-frame@^6.26.0:
esutils "^2.0.2"
js-tokens "^3.0.2"
-babel-eslint@^9.0.0:
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-9.0.0.tgz#7d9445f81ed9f60aff38115f838970df9f2b6220"
- integrity sha512-itv1MwE3TMbY0QtNfeL7wzak1mV47Uy+n6HtSOO4Xd7rvmO+tsGQSgyOEEgo6Y2vHZKZphaoelNeSVj4vkLA1g==
+babel-eslint@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.0.1.tgz#919681dc099614cd7d31d45c8908695092a1faed"
+ integrity sha512-z7OT1iNV+TjOwHNLLyJk+HN+YVWX+CLE6fPD2SymJZOZQBs+QIexFjhm4keGTm8MW9xr4EC9Q0PbaLB24V5GoQ==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/parser" "^7.0.0"