summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/activities.js10
-rw-r--r--app/assets/javascripts/behaviors/copy_to_clipboard.js2
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.js8
-rw-r--r--app/assets/javascripts/behaviors/markdown/copy_as_gfm.js107
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_math.js8
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js58
-rw-r--r--app/assets/javascripts/behaviors/preview_markdown.js111
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js40
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js3
-rw-r--r--app/assets/javascripts/behaviors/secret_values.js8
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js30
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js4
-rw-r--r--app/assets/javascripts/blob/3d_viewer/index.js58
-rw-r--r--app/assets/javascripts/blob/3d_viewer/mesh_object.js21
-rw-r--r--app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js2
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js28
-rw-r--r--app/assets/javascripts/blob/blob_line_permalink_updater.js18
-rw-r--r--app/assets/javascripts/blob/file_template_selector.js8
-rw-r--r--app/assets/javascripts/blob/notebook/index.js7
-rw-r--r--app/assets/javascripts/blob/sketch/index.js2
-rw-r--r--app/assets/javascripts/blob/stl_viewer.js4
-rw-r--r--app/assets/javascripts/blob/template_selector.js8
-rw-r--r--app/assets/javascripts/blob/template_selectors/license_selector.js2
-rw-r--r--app/assets/javascripts/blob/template_selectors/type_selector.js1
-rw-r--r--app/assets/javascripts/blob/viewer/index.js42
-rw-r--r--app/assets/javascripts/breadcrumb.js15
-rw-r--r--app/assets/javascripts/build_artifacts.js18
-rw-r--r--app/assets/javascripts/ci_variable_list/ajax_variable_list.js56
-rw-r--r--app/assets/javascripts/ci_variable_list/ci_variable_list.js37
-rw-r--r--app/assets/javascripts/ci_variable_list/native_form_variable_list.js5
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue293
-rw-r--r--app/assets/javascripts/clusters/constants.js1
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js8
-rw-r--r--app/assets/javascripts/comment_type_toggle.js56
-rw-r--r--app/assets/javascripts/commit/image_file.js320
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_bundle.js10
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.vue122
-rw-r--r--app/assets/javascripts/commit_merge_requests.js7
-rw-r--r--app/assets/javascripts/commits.js38
-rw-r--r--app/assets/javascripts/commons/bootstrap.js12
-rw-r--r--app/assets/javascripts/compare_autocomplete.js2
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js24
-rw-r--r--app/assets/javascripts/contextual_sidebar.js7
-rw-r--r--app/assets/javascripts/create_item_dropdown.js13
-rw-r--r--app/assets/javascripts/create_label.js63
-rw-r--r--app/assets/javascripts/cycle_analytics/components/banner.vue40
-rw-r--r--app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue22
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_code_component.vue36
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_component.vue37
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue47
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.vue40
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue50
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.vue54
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time_component.vue24
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js24
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_service.js5
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_store.js36
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue6
-rw-r--r--app/assets/javascripts/deploy_keys/service/index.js9
-rw-r--r--app/assets/javascripts/diff.js14
-rw-r--r--app/assets/javascripts/diff_notes/components/comment_resolve_btn.js30
-rw-r--r--app/assets/javascripts/diff_notes/components/diff_note_avatars.js28
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js35
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_count.js10
-rw-r--r--app/assets/javascripts/diff_notes/mixins/discussion.js10
-rw-r--r--app/assets/javascripts/diff_notes/models/discussion.js18
-rw-r--r--app/assets/javascripts/diff_notes/stores/comments.js14
-rw-r--r--app/assets/javascripts/diffs/components/app.vue18
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue4
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue12
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue116
-rw-r--r--app/assets/javascripts/diffs/store/actions.js24
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js94
-rw-r--r--app/assets/javascripts/droplab/constants.js9
-rw-r--r--app/assets/javascripts/droplab/drop_down.js4
-rw-r--r--app/assets/javascripts/droplab/drop_lab.js5
-rw-r--r--app/assets/javascripts/droplab/keyboard.js53
-rw-r--r--app/assets/javascripts/droplab/plugins/ajax.js4
-rw-r--r--app/assets/javascripts/droplab/plugins/ajax_filter.js18
-rw-r--r--app/assets/javascripts/droplab/plugins/filter.js39
-rw-r--r--app/assets/javascripts/droplab/plugins/input_setter.js4
-rw-r--r--app/assets/javascripts/droplab/utils.js15
-rw-r--r--app/assets/javascripts/dropzone_input.js41
-rw-r--r--app/assets/javascripts/due_date_select.js3
-rw-r--r--app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js55
-rw-r--r--app/assets/javascripts/environments/components/container.vue64
-rw-r--r--app/assets/javascripts/environments/components/empty_state.vue61
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.vue4
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js55
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue71
-rw-r--r--app/assets/javascripts/environments/index.js63
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js20
-rw-r--r--app/assets/javascripts/experimental_flags.js2
-rw-r--r--app/assets/javascripts/feature_highlight/feature_highlight.js16
-rw-r--r--app/assets/javascripts/feature_highlight/feature_highlight_helper.js18
-rw-r--r--app/assets/javascripts/files_comment_button.js12
-rw-r--r--app/assets/javascripts/filterable_list.js25
-rw-r--r--app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js33
-rw-r--r--app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue8
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_emoji.js15
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js19
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js8
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js54
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js12
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js28
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_tokenizer.js63
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js125
-rw-r--r--app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js137
-rw-r--r--app/assets/javascripts/filtered_search/recent_searches_root.js11
-rw-r--r--app/assets/javascripts/filtered_search/stores/recent_searches_store.js13
-rw-r--r--app/assets/javascripts/flash.js42
-rw-r--r--app/assets/javascripts/fly_out_nav.js52
-rw-r--r--app/assets/javascripts/gl_field_error.js3
-rw-r--r--app/assets/javascripts/gl_field_errors.js14
-rw-r--r--app/assets/javascripts/gl_form.js19
-rw-r--r--app/assets/javascripts/group_avatar.js5
-rw-r--r--app/assets/javascripts/group_label_subscription.js10
-rw-r--r--app/assets/javascripts/groups/components/item_stats.vue72
-rw-r--r--app/assets/javascripts/groups/components/item_stats_value.vue86
-rw-r--r--app/assets/javascripts/groups/new_group_child.js22
-rw-r--r--app/assets/javascripts/groups/store/groups_store.js23
-rw-r--r--app/assets/javascripts/groups/transfer_dropdown.js2
-rw-r--r--app/assets/javascripts/groups_select.js9
-rw-r--r--app/assets/javascripts/helpers/avatar_helper.js4
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue38
-rw-r--r--app/assets/javascripts/image_diff/image_diff.js6
-rw-r--r--app/assets/javascripts/image_diff/init_discussion_tab.js3
-rw-r--r--app/assets/javascripts/image_diff/replaced_image_diff.js15
-rw-r--r--app/assets/javascripts/importer_status.js122
-rw-r--r--app/assets/javascripts/init_changes_dropdown.js2
-rw-r--r--app/assets/javascripts/init_notes.js8
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js5
-rw-r--r--app/assets/javascripts/issuable/auto_width_dropdown_select.js5
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js10
-rw-r--r--app/assets/javascripts/issuable_form.js34
-rw-r--r--app/assets/javascripts/issuable_index.js9
-rw-r--r--app/assets/javascripts/issue.js73
-rw-r--r--app/assets/javascripts/job.js190
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue275
-rw-r--r--app/assets/javascripts/jobs/components/job_container_item.vue65
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue54
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue8
-rw-r--r--app/assets/javascripts/jobs/components/jobs_container.vue53
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue335
-rw-r--r--app/assets/javascripts/jobs/components/stuck_block.vue16
-rw-r--r--app/assets/javascripts/jobs/index.js26
-rw-r--r--app/assets/javascripts/jobs/job_details_bundle.js76
-rw-r--r--app/assets/javascripts/jobs/store/actions.js117
-rw-r--r--app/assets/javascripts/jobs/store/getters.js17
-rw-r--r--app/assets/javascripts/jobs/store/mutation_types.js19
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js64
-rw-r--r--app/assets/javascripts/jobs/store/state.js29
-rw-r--r--app/assets/javascripts/jobs/svg/scroll_down.svg5
-rw-r--r--app/assets/javascripts/label_manager.js14
-rw-r--r--app/assets/javascripts/labels.js2
-rw-r--r--app/assets/javascripts/layout_nav.js60
-rw-r--r--app/assets/javascripts/lib/utils/datefix.js28
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js118
-rw-r--r--app/assets/javascripts/lib/utils/pretty_time.js63
-rw-r--r--app/assets/javascripts/line_highlighter.js28
-rw-r--r--app/assets/javascripts/locale/sprintf.js2
-rw-r--r--app/assets/javascripts/member_expiration_date.js10
-rw-r--r--app/assets/javascripts/merge_request.js23
-rw-r--r--app/assets/javascripts/merge_request_tabs.js2
-rw-r--r--app/assets/javascripts/milestone.js34
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js17
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue5
-rw-r--r--app/assets/javascripts/mr_notes/index.js2
-rw-r--r--app/assets/javascripts/namespace_select.js8
-rw-r--r--app/assets/javascripts/network/branch_graph.js162
-rw-r--r--app/assets/javascripts/network/raphael.js2
-rw-r--r--app/assets/javascripts/new_branch_form.js14
-rw-r--r--app/assets/javascripts/new_commit_form.js4
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue5
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue82
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue11
-rw-r--r--app/assets/javascripts/notes/discussion_filters.js33
-rw-r--r--app/assets/javascripts/notes/index.js3
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js5
-rw-r--r--app/assets/javascripts/notes/stores/actions.js25
-rw-r--r--app/assets/javascripts/notes/stores/getters.js6
-rw-r--r--app/assets/javascripts/notes/stores/modules/index.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js4
-rw-r--r--app/assets/javascripts/notes/stores/utils.js12
-rw-r--r--app/assets/javascripts/notifications_dropdown.js4
-rw-r--r--app/assets/javascripts/notifications_form.js10
-rw-r--r--app/assets/javascripts/pager.js44
-rw-r--r--app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js2
-rw-r--r--app/assets/javascripts/pages/admin/admin.js15
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/account_and_limits.js8
-rw-r--r--app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js36
-rw-r--r--app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue65
-rw-r--r--app/assets/javascripts/pages/admin/projects/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue139
-rw-r--r--app/assets/javascripts/pages/admin/projects/index/index.js2
-rw-r--r--app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue187
-rw-r--r--app/assets/javascripts/pages/admin/users/index.js6
-rw-r--r--app/assets/javascripts/pages/admin/users/new/index.js6
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/todos.js15
-rw-r--r--app/assets/javascripts/pages/groups/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue169
-rw-r--r--app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js16
-rw-r--r--app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js16
-rw-r--r--app/assets/javascripts/pages/profiles/index.js7
-rw-r--r--app/assets/javascripts/pages/profiles/two_factor_auths/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/branches/new/index.js11
-rw-r--r--app/assets/javascripts/pages/projects/graphs/charts/index.js22
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js91
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js221
-rw-r--r--app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js64
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js3
-rw-r--r--app/assets/javascripts/pages/projects/init_form.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/show.js2
-rw-r--r--app/assets/javascripts/pages/projects/jobs/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue124
-rw-r--r--app/assets/javascripts/pages/projects/labels/index/index.js16
-rw-r--r--app/assets/javascripts/pages/projects/network/network.js10
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js22
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue102
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue46
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js4
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/charts/index.js59
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/index/index.js66
-rw-r--r--app/assets/javascripts/pages/projects/pipelines/init_pipelines.js7
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue114
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue36
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/constants.js6
-rw-r--r--app/assets/javascripts/pages/projects/shared/project_avatar.js5
-rw-r--r--app/assets/javascripts/pages/projects/wikis/index.js3
-rw-r--r--app/assets/javascripts/pages/search/show/search.js25
-rw-r--r--app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js2
-rw-r--r--app/assets/javascripts/pages/sessions/new/username_validator.js10
-rw-r--r--app/assets/javascripts/pages/users/index.js6
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js2
-rw-r--r--app/assets/javascripts/pdf/index.vue103
-rw-r--r--app/assets/javascripts/pdf/page/index.vue103
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue (renamed from app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue)54
-rw-r--r--app/assets/javascripts/pipelines/components/graph/job_item.vue (renamed from app/assets/javascripts/pipelines/components/graph/job_component.vue)0
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue36
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/stage.vue6
-rw-r--r--app/assets/javascripts/project_find_file.js95
-rw-r--r--app/assets/javascripts/project_import.js1
-rw-r--r--app/assets/javascripts/project_label_subscription.js43
-rw-r--r--app/assets/javascripts/project_select.js49
-rw-r--r--app/assets/javascripts/project_select_combo_button.js27
-rw-r--r--app/assets/javascripts/project_visibility.js5
-rw-r--r--app/assets/javascripts/prometheus_metrics/prometheus_metrics.js36
-rw-r--r--app/assets/javascripts/raven/raven_config.js2
-rw-r--r--app/assets/javascripts/ref_select_dropdown.js5
-rw-r--r--app/assets/javascripts/reports/components/grouped_test_reports_app.vue2
-rw-r--r--app/assets/javascripts/settings_panels.js11
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue2
-rw-r--r--app/assets/javascripts/single_file_diff.js34
-rw-r--r--app/assets/javascripts/smart_interval.js20
-rw-r--r--app/assets/javascripts/star.js8
-rw-r--r--app/assets/javascripts/task_list.js9
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js18
-rw-r--r--app/assets/javascripts/terminal/terminal.js12
-rw-r--r--app/assets/javascripts/test_utils/simulate_drag.js56
-rw-r--r--app/assets/javascripts/test_utils/simulate_input.js2
-rw-r--r--app/assets/javascripts/toggle_buttons.js2
-rw-r--r--app/assets/javascripts/tree.js18
-rw-r--r--app/assets/javascripts/u2f/authenticate.js14
-rw-r--r--app/assets/javascripts/u2f/register.js13
-rw-r--r--app/assets/javascripts/u2f/util.js5
-rw-r--r--app/assets/javascripts/ui_development_kit.js18
-rw-r--r--app/assets/javascripts/usage_ping_consent.js5
-rw-r--r--app/assets/javascripts/users_select.js1095
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue75
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue18
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_badge_link.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue34
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue143
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_countdown.vue49
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue28
-rw-r--r--app/assets/javascripts/vue_shared/components/pikaday.vue2
-rw-r--r--app/assets/javascripts/zen_mode.js32
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss11
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/common.scss10
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss1
-rw-r--r--app/assets/stylesheets/framework/header.scss19
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss1
-rw-r--r--app/assets/stylesheets/framework/page_title.scss18
-rw-r--r--app/assets/stylesheets/framework/panels.scss5
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss16
-rw-r--r--app/assets/stylesheets/framework/terms.scss15
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/pages/builds.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss14
-rw-r--r--app/assets/stylesheets/pages/issues.scss23
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss25
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss21
-rw-r--r--app/controllers/admin/applications_controller.rb8
-rw-r--r--app/controllers/admin/dashboard_controller.rb4
-rw-r--r--app/controllers/autocomplete_controller.rb2
-rw-r--r--app/controllers/boards/issues_controller.rb17
-rw-r--r--app/controllers/concerns/issuable_actions.rb33
-rw-r--r--app/controllers/concerns/notes_actions.rb17
-rw-r--r--app/controllers/concerns/sends_blob.rb2
-rw-r--r--app/controllers/import/github_controller.rb2
-rw-r--r--app/controllers/profiles/keys_controller.rb2
-rw-r--r--app/controllers/projects/avatars_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb20
-rw-r--r--app/controllers/projects/merge_requests_controller.rb43
-rw-r--r--app/controllers/projects/mirrors_controller.rb16
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb23
-rw-r--r--app/controllers/snippets_controller.rb6
-rw-r--r--app/finders/applications_finder.rb22
-rw-r--r--app/finders/issuable_finder.rb4
-rw-r--r--app/finders/notes_finder.rb6
-rw-r--r--app/finders/user_finder.rb52
-rw-r--r--app/finders/users_finder.rb4
-rw-r--r--app/helpers/blob_helper.rb20
-rw-r--r--app/helpers/ci_status_helper.rb2
-rw-r--r--app/helpers/count_helper.rb14
-rw-r--r--app/helpers/groups_helper.rb9
-rw-r--r--app/helpers/issuables_helper.rb4
-rw-r--r--app/helpers/time_helper.rb16
-rw-r--r--app/helpers/workhorse_helper.rb2
-rw-r--r--app/models/ci/job_artifact.rb27
-rw-r--r--app/models/ci/pipeline.rb10
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb3
-rw-r--r--app/models/clusters/kubernetes_namespace.rb49
-rw-r--r--app/models/clusters/platforms/kubernetes.rb22
-rw-r--r--app/models/clusters/project.rb3
-rw-r--r--app/models/concerns/relative_positioning.rb79
-rw-r--r--app/models/environment_status.rb56
-rw-r--r--app/models/forked_project_link.rb6
-rw-r--r--app/models/list.rb1
-rw-r--r--app/models/note.rb9
-rw-r--r--app/models/project.rb105
-rw-r--r--app/models/project_services/bamboo_service.rb13
-rw-r--r--app/models/project_services/flowdock_service.rb48
-rw-r--r--app/models/project_services/jira_service.rb7
-rw-r--r--app/models/project_services/kubernetes_service.rb6
-rw-r--r--app/models/project_services/microsoft_teams_service.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb1
-rw-r--r--app/models/repository.rb19
-rw-r--r--app/models/ssh_host_key.rb130
-rw-r--r--app/models/tree.rb51
-rw-r--r--app/models/user.rb10
-rw-r--r--app/models/user_preference.rb52
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/presenters/ci/build_runner_presenter.rb10
-rw-r--r--app/serializers/build_details_entity.rb1
-rw-r--r--app/serializers/current_user_entity.rb8
-rw-r--r--app/serializers/environment_status_entity.rb62
-rw-r--r--app/serializers/environment_status_serializer.rb5
-rw-r--r--app/serializers/merge_request_user_entity.rb2
-rw-r--r--app/serializers/user_preference_entity.rb10
-rw-r--r--app/services/audit_event_service.rb26
-rw-r--r--app/services/boards/lists/list_service.rb2
-rw-r--r--app/services/clusters/gcp/finalize_creation_service.rb7
-rw-r--r--app/services/groups/update_service.rb6
-rw-r--r--app/services/projects/after_rename_service.rb135
-rw-r--r--app/services/projects/create_service.rb21
-rw-r--r--app/services/projects/fork_service.rb68
-rw-r--r--app/services/projects/forks_count_service.rb5
-rw-r--r--app/services/projects/move_forks_service.rb13
-rw-r--r--app/services/projects/unlink_fork_service.rb1
-rw-r--r--app/services/projects/update_service.rb2
-rw-r--r--app/services/quick_actions/interpret_service.rb2
-rw-r--r--app/services/web_hook_service.rb8
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml9
-rw-r--r--app/views/admin/projects/show.html.haml2
-rw-r--r--app/views/dashboard/_activity_head.html.haml3
-rw-r--r--app/views/dashboard/_groups_head.html.haml9
-rw-r--r--app/views/dashboard/_projects_head.html.haml9
-rw-r--r--app/views/dashboard/_snippets_head.html.haml11
-rw-r--r--app/views/dashboard/issues.html.haml8
-rw-r--r--app/views/dashboard/merge_requests.html.haml9
-rw-r--r--app/views/dashboard/milestones/index.html.haml12
-rw-r--r--app/views/dashboard/todos/index.html.haml3
-rw-r--r--app/views/groups/edit.html.haml2
-rw-r--r--app/views/groups/new.html.haml3
-rw-r--r--app/views/instance_statistics/conversational_development_index/_disabled.html.haml2
-rw-r--r--app/views/layouts/_flash.html.haml2
-rw-r--r--app/views/layouts/_page.html.haml5
-rw-r--r--app/views/layouts/dashboard.html.haml1
-rw-r--r--app/views/layouts/explore.html.haml2
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml15
-rw-r--r--app/views/layouts/header/_default.html.haml6
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml14
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml4
-rw-r--r--app/views/layouts/terms.html.haml29
-rw-r--r--app/views/projects/_md_preview.html.haml4
-rw-r--r--app/views/projects/_wiki.html.haml16
-rw-r--r--app/views/projects/artifacts/browse.html.haml2
-rw-r--r--app/views/projects/artifacts/file.html.haml2
-rw-r--r--app/views/projects/blob/_template_selectors.html.haml8
-rw-r--r--app/views/projects/blob/edit.html.haml2
-rw-r--r--app/views/projects/blob/new.html.haml2
-rw-r--r--app/views/projects/branches/_panel.html.haml3
-rw-r--r--app/views/projects/ci/builds/_build.html.haml4
-rw-r--r--app/views/projects/issues/_discussion.html.haml2
-rw-r--r--app/views/projects/issues/_new_branch.html.haml3
-rw-r--r--app/views/projects/issues/show.html.haml7
-rw-r--r--app/views/projects/jobs/_header.html.haml10
-rw-r--r--app/views/projects/jobs/show.html.haml51
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/show.html.haml6
-rw-r--r--app/views/projects/new.html.haml3
-rw-r--r--app/views/projects/pipelines/_info.html.haml6
-rw-r--r--app/views/projects/protected_branches/shared/_branches_list.html.haml3
-rw-r--r--app/views/projects/protected_branches/shared/_create_protected_branch.html.haml3
-rw-r--r--app/views/projects/protected_tags/shared/_create_protected_tag.html.haml3
-rw-r--r--app/views/projects/protected_tags/shared/_tags_list.html.haml3
-rw-r--r--app/views/projects/registry/repositories/index.html.haml3
-rw-r--r--app/views/projects/services/prometheus/_metrics.html.haml12
-rw-r--r--app/views/projects/tags/new.html.haml3
-rw-r--r--app/views/projects/tree/_tree_commit_column.html.haml2
-rw-r--r--app/views/projects/tree/_tree_content.html.haml2
-rw-r--r--app/views/projects/triggers/_index.html.haml3
-rw-r--r--app/views/projects/wikis/_pages_wiki_page.html.haml3
-rw-r--r--app/views/projects/wikis/show.html.haml5
-rw-r--r--app/views/shared/_field.html.haml2
-rw-r--r--app/views/shared/_user_dropdown_contributing_link.html.haml2
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml15
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar_todo.html.haml4
-rw-r--r--app/views/shared/labels/_form.html.haml8
-rw-r--r--app/views/shared/runners/show.html.haml2
-rw-r--r--app/views/snippets/new.html.haml13
-rw-r--r--app/workers/namespaceless_project_destroy_worker.rb2
440 files changed, 7396 insertions, 5400 deletions
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js
index de4566bb119..05de970e387 100644
--- a/app/assets/javascripts/activities.js
+++ b/app/assets/javascripts/activities.js
@@ -6,10 +6,12 @@ import Pager from './pager';
import { localTimeAgo } from './lib/utils/datetime_utility';
export default class Activities {
- constructor() {
- Pager.init(20, true, false, data => data, this.updateTooltips);
+ constructor(container = '') {
+ this.container = container;
- $('.event-filter-link').on('click', (e) => {
+ Pager.init(20, true, false, data => data, this.updateTooltips, this.container);
+
+ $('.event-filter-link').on('click', e => {
e.preventDefault();
this.toggleFilter(e.currentTarget);
this.reloadActivities();
@@ -22,7 +24,7 @@ export default class Activities {
reloadActivities() {
$('.content_list').html('');
- Pager.init(20, true, false, data => data, this.updateTooltips);
+ Pager.init(20, true, false, data => data, this.updateTooltips, this.container);
}
toggleFilter(sender) {
diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js
index 00419e80cbb..9a33a060c76 100644
--- a/app/assets/javascripts/behaviors/copy_to_clipboard.js
+++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js
@@ -51,7 +51,7 @@ export default function initCopyToClipboard() {
* the last minute to deconstruct this JSON hash and set the `text/plain` and `text/x-gfm` copy
* data types to the intended values.
*/
- $(document).on('copy', 'body > textarea[readonly]', (e) => {
+ $(document).on('copy', 'body > textarea[readonly]', e => {
const { clipboardData } = e.originalEvent;
if (!clipboardData) return;
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
index 1d63f5baeee..9bdfc21c7e4 100644
--- a/app/assets/javascripts/behaviors/details_behavior.js
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -2,7 +2,9 @@ import $ from 'jquery';
$(() => {
$('body').on('click', '.js-details-target', function target() {
- $(this).closest('.js-details-container').toggleClass('open');
+ $(this)
+ .closest('.js-details-container')
+ .toggleClass('open');
});
// Show details content. Hides link after click.
@@ -13,7 +15,9 @@ $(() => {
//
$('body').on('click', '.js-details-expand', function expand(e) {
e.preventDefault();
- $(this).next('.js-details-content').removeClass('hide');
+ $(this)
+ .next('.js-details-content')
+ .removeClass('hide');
$(this).hide();
const truncatedItem = $(this).siblings('.js-details-short');
diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
index 0d7e8a5a3cb..fe02096d903 100644
--- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js
@@ -34,7 +34,7 @@ const gfmRules = {
},
},
AutolinkFilter: {
- 'a'(el, text) {
+ a(el, text) {
// Fallback on the regular MarkdownFilter's `a` handler.
if (text !== el.getAttribute('href')) return false;
@@ -60,7 +60,7 @@ const gfmRules = {
},
},
ImageLazyLoadFilter: {
- 'img'(el, text) {
+ img(el, text) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
},
},
@@ -71,7 +71,7 @@ const gfmRules = {
return CopyAsGFM.nodeToGFM(videoEl);
},
- 'video'(el) {
+ video(el) {
return `![${el.dataset.title}](${el.getAttribute('src')})`;
},
},
@@ -118,11 +118,14 @@ const gfmRules = {
'a[name]:not([href]):empty'(el) {
return el.outerHTML;
},
- 'dl'(el, text) {
- let lines = text.replace(/\n\n/g, '\n').trim().split('\n');
+ dl(el, text) {
+ let lines = text
+ .replace(/\n\n/g, '\n')
+ .trim()
+ .split('\n');
// Add two spaces to the front of subsequent list items lines,
// or leave the line entirely blank.
- lines = lines.map((l) => {
+ lines = lines.map(l => {
const line = l.trim();
if (line.length === 0) return '';
@@ -151,27 +154,30 @@ const gfmRules = {
// Prefixes lines with 4 spaces if the code contains triple backticks
if (lang === '' && text.match(/^```/gm)) {
- return text.split('\n').map((l) => {
- const line = l.trim();
- if (line.length === 0) return '';
-
- return ` ${line}`;
- }).join('\n');
+ return text
+ .split('\n')
+ .map(l => {
+ const line = l.trim();
+ if (line.length === 0) return '';
+
+ return ` ${line}`;
+ })
+ .join('\n');
}
return `\`\`\`${lang}\n${text}\n\`\`\``;
},
'pre > code'(el, text) {
- // Don't wrap code blocks in ``
+ // Don't wrap code blocks in ``
return text;
},
},
MarkdownFilter: {
- 'br'(el) {
+ br(el) {
// Two spaces at the end of a line are turned into a BR
return ' ';
},
- 'code'(el, text) {
+ code(el, text) {
let backtickCount = 1;
const backtickMatch = text.match(/`+/);
if (backtickMatch) {
@@ -183,27 +189,31 @@ const gfmRules = {
return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
},
- 'blockquote'(el, text) {
- return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
+ blockquote(el, text) {
+ return text
+ .trim()
+ .split('\n')
+ .map(s => `> ${s}`.trim())
+ .join('\n');
},
- 'img'(el) {
+ img(el) {
const imageSrc = el.src;
- const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || '');
+ const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : el.dataset.src || '';
return `![${el.getAttribute('alt')}](${imageUrl})`;
},
'a.anchor'(el, text) {
// Don't render a Markdown link for the anchor link inside a heading
return text;
},
- 'a'(el, text) {
+ a(el, text) {
return `[${text}](${el.getAttribute('href')})`;
},
- 'li'(el, text) {
+ li(el, text) {
const lines = text.trim().split('\n');
const firstLine = `- ${lines.shift()}`;
// Add four spaces to the front of subsequent list items lines,
// or leave the line entirely blank.
- const nextLines = lines.map((s) => {
+ const nextLines = lines.map(s => {
if (s.trim().length === 0) return '';
return ` ${s}`;
@@ -211,49 +221,49 @@ const gfmRules = {
return `${firstLine}\n${nextLines.join('\n')}`;
},
- 'ul'(el, text) {
+ ul(el, text) {
return text;
},
- 'ol'(el, text) {
+ ol(el, text) {
// LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists.
- return text.replace(/^- /mg, '1. ');
+ return text.replace(/^- /gm, '1. ');
},
- 'h1'(el, text) {
+ h1(el, text) {
return `# ${text.trim()}\n`;
},
- 'h2'(el, text) {
+ h2(el, text) {
return `## ${text.trim()}\n`;
},
- 'h3'(el, text) {
+ h3(el, text) {
return `### ${text.trim()}\n`;
},
- 'h4'(el, text) {
+ h4(el, text) {
return `#### ${text.trim()}\n`;
},
- 'h5'(el, text) {
+ h5(el, text) {
return `##### ${text.trim()}\n`;
},
- 'h6'(el, text) {
+ h6(el, text) {
return `###### ${text.trim()}\n`;
},
- 'strong'(el, text) {
+ strong(el, text) {
return `**${text}**`;
},
- 'em'(el, text) {
+ em(el, text) {
return `_${text}_`;
},
- 'del'(el, text) {
+ del(el, text) {
return `~~${text}~~`;
},
- 'hr'(el) {
+ hr(el) {
// extra leading \n is to ensure that there is a blank line between
// a list followed by an hr, otherwise this breaks old redcarpet rendering
return '\n-----\n';
},
- 'p'(el, text) {
+ p(el, text) {
return `${text.trim()}\n`;
},
- 'table'(el) {
+ table(el) {
const theadEl = el.querySelector('thead');
const tbodyEl = el.querySelector('tbody');
if (!theadEl || !tbodyEl) return false;
@@ -263,8 +273,8 @@ const gfmRules = {
return [theadText, tbodyText].join('\n');
},
- 'thead'(el, text) {
- const cells = _.map(el.querySelectorAll('th'), (cell) => {
+ thead(el, text) {
+ const cells = _.map(el.querySelectorAll('th'), cell => {
let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
let before = '';
@@ -296,7 +306,7 @@ const gfmRules = {
return [text, separatorRow].join('\n');
},
- 'tr'(el) {
+ tr(el) {
const cellEls = el.querySelectorAll('td, th');
if (cellEls.length === 0) return false;
@@ -315,8 +325,12 @@ export class CopyAsGFM {
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
if (isIOS) return;
- $(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
- $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
+ $(document).on('copy', '.md, .wiki', e => {
+ CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection);
+ });
+ $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', e => {
+ CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection);
+ });
$(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
}
@@ -356,7 +370,7 @@ export class CopyAsGFM {
// This will break down when the actual code block contains an uneven
// number of backticks, but this is a rare edge case.
const backtickMatch = textBefore.match(/`/g);
- const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1;
+ const insideCodeBlock = backtickMatch && backtickMatch.length % 2 === 1;
if (insideCodeBlock) {
return text;
@@ -393,7 +407,9 @@ export class CopyAsGFM {
let lineSelector = '.line';
if (target) {
- const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0];
+ const lineClass = ['left-side', 'right-side'].filter(name =>
+ target.classList.contains(name),
+ )[0];
if (lineClass) {
lineSelector = `.line_content.${lineClass} ${lineSelector}`;
}
@@ -436,7 +452,8 @@ export class CopyAsGFM {
return node.textContent;
}
- const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
+ const respectWhitespace =
+ respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
const text = this.innerGFM(node, respectWhitespace);
diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js
index eb4e59d12b1..a68936d79e2 100644
--- a/app/assets/javascripts/behaviors/markdown/render_math.js
+++ b/app/assets/javascripts/behaviors/markdown/render_math.js
@@ -32,7 +32,9 @@ export default function renderMath($els) {
Promise.all([
import(/* webpackChunkName: 'katex' */ 'katex'),
import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
- ]).then(([katex]) => {
- renderWithKaTeX($els, katex);
- }).catch(() => flash(__('An error occurred while rendering KaTeX')));
+ ])
+ .then(([katex]) => {
+ renderWithKaTeX($els, katex);
+ })
+ .catch(() => flash(__('An error occurred while rendering KaTeX')));
}
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index 56b1896e9f1..720f30e18e6 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -17,41 +17,43 @@ import flash from '~/flash';
export default function renderMermaid($els) {
if (!$els.length) return;
- import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
- mermaid.initialize({
- // mermaid core options
- mermaid: {
- startOnLoad: false,
- },
- // mermaidAPI options
- theme: 'neutral',
- });
+ import(/* webpackChunkName: 'mermaid' */ 'mermaid')
+ .then(mermaid => {
+ mermaid.initialize({
+ // mermaid core options
+ mermaid: {
+ startOnLoad: false,
+ },
+ // mermaidAPI options
+ theme: 'neutral',
+ });
- $els.each((i, el) => {
- const source = el.textContent;
+ $els.each((i, el) => {
+ const source = el.textContent;
- // Remove any extra spans added by the backend syntax highlighting.
- Object.assign(el, { textContent: source });
+ // Remove any extra spans added by the backend syntax highlighting.
+ Object.assign(el, { textContent: source });
- mermaid.init(undefined, el, (id) => {
- const svg = document.getElementById(id);
+ mermaid.init(undefined, el, id => {
+ const svg = document.getElementById(id);
- svg.classList.add('mermaid');
+ svg.classList.add('mermaid');
- // pre > code > svg
- svg.closest('pre').replaceWith(svg);
+ // pre > code > svg
+ svg.closest('pre').replaceWith(svg);
- // We need to add the original source into the DOM to allow Copy-as-GFM
- // to access it.
- const sourceEl = document.createElement('text');
- sourceEl.classList.add('source');
- sourceEl.setAttribute('display', 'none');
- sourceEl.textContent = source;
+ // We need to add the original source into the DOM to allow Copy-as-GFM
+ // to access it.
+ const sourceEl = document.createElement('text');
+ sourceEl.classList.add('source');
+ sourceEl.setAttribute('display', 'none');
+ sourceEl.textContent = source;
- svg.appendChild(sourceEl);
+ svg.appendChild(sourceEl);
+ });
});
+ })
+ .catch(err => {
+ flash(`Can't load mermaid module: ${err}`);
});
- }).catch((err) => {
- flash(`Can't load mermaid module: ${err}`);
- });
}
diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js
index 0964baf8954..35f1bb6b080 100644
--- a/app/assets/javascripts/behaviors/preview_markdown.js
+++ b/app/assets/javascripts/behaviors/preview_markdown.js
@@ -26,7 +26,7 @@ MarkdownPreview.prototype.emptyMessage = 'Nothing to preview.';
MarkdownPreview.prototype.ajaxCache = {};
-MarkdownPreview.prototype.showPreview = function ($form) {
+MarkdownPreview.prototype.showPreview = function($form) {
var mdText;
var markdownVersion;
var url;
@@ -44,34 +44,40 @@ MarkdownPreview.prototype.showPreview = function ($form) {
this.hideReferencedUsers($form);
} else {
preview.addClass('md-preview-loading').text('Loading...');
- this.fetchMarkdownPreview(mdText, url, (function (response) {
- var body;
- if (response.body.length > 0) {
- ({ body } = response);
- } else {
- body = this.emptyMessage;
- }
-
- preview.removeClass('md-preview-loading').html(body);
- preview.renderGFM();
- this.renderReferencedUsers(response.references.users, $form);
-
- if (response.references.commands) {
- this.renderReferencedCommands(response.references.commands, $form);
- }
- }).bind(this));
+ this.fetchMarkdownPreview(
+ mdText,
+ url,
+ function(response) {
+ var body;
+ if (response.body.length > 0) {
+ ({ body } = response);
+ } else {
+ body = this.emptyMessage;
+ }
+
+ preview.removeClass('md-preview-loading').html(body);
+ preview.renderGFM();
+ this.renderReferencedUsers(response.references.users, $form);
+
+ if (response.references.commands) {
+ this.renderReferencedCommands(response.references.commands, $form);
+ }
+ }.bind(this),
+ );
}
};
-MarkdownPreview.prototype.versionedPreviewPath = function (markdownPreviewPath, markdownVersion) {
+MarkdownPreview.prototype.versionedPreviewPath = function(markdownPreviewPath, markdownVersion) {
if (typeof markdownVersion === 'undefined') {
return markdownPreviewPath;
}
- return `${markdownPreviewPath}${markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'}markdown_version=${markdownVersion}`;
+ return `${markdownPreviewPath}${
+ markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
+ }markdown_version=${markdownVersion}`;
};
-MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
+MarkdownPreview.prototype.fetchMarkdownPreview = function(text, url, success) {
if (!url) {
return;
}
@@ -79,24 +85,25 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
success(this.ajaxCache.response);
return;
}
- axios.post(url, {
- text,
- })
- .then(({ data }) => {
- this.ajaxCache = {
- text: text,
- response: data,
- };
- success(data);
- })
- .catch(() => flash(__('An error occurred while fetching markdown preview')));
+ axios
+ .post(url, {
+ text,
+ })
+ .then(({ data }) => {
+ this.ajaxCache = {
+ text: text,
+ response: data,
+ };
+ success(data);
+ })
+ .catch(() => flash(__('An error occurred while fetching markdown preview')));
};
-MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
+MarkdownPreview.prototype.hideReferencedUsers = function($form) {
$form.find('.referenced-users').hide();
};
-MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
+MarkdownPreview.prototype.renderReferencedUsers = function(users, $form) {
var referencedUsers;
referencedUsers = $form.find('.referenced-users');
if (referencedUsers.length) {
@@ -109,11 +116,11 @@ MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
}
};
-MarkdownPreview.prototype.hideReferencedCommands = function ($form) {
+MarkdownPreview.prototype.hideReferencedCommands = function($form) {
$form.find('.referenced-commands').hide();
};
-MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form) {
+MarkdownPreview.prototype.renderReferencedCommands = function(commands, $form) {
var referencedCommands;
referencedCommands = $form.find('.referenced-commands');
if (commands.length > 0) {
@@ -132,14 +139,14 @@ writeButtonSelector = '.js-md-write-button';
lastTextareaPreviewed = null;
const markdownToolbar = $('.md-header-toolbar');
-$.fn.setupMarkdownPreview = function () {
+$.fn.setupMarkdownPreview = function() {
var $form = $(this);
- $form.find('textarea.markdown-area').on('input', function () {
+ $form.find('textarea.markdown-area').on('input', function() {
markdownPreview.hideReferencedUsers($form);
});
};
-$(document).on('markdown-preview:show', function (e, $form) {
+$(document).on('markdown-preview:show', function(e, $form) {
if (!$form) {
return;
}
@@ -148,8 +155,14 @@ $(document).on('markdown-preview:show', function (e, $form) {
lastTextareaHeight = lastTextareaPreviewed.height();
// toggle tabs
- $form.find(writeButtonSelector).parent().removeClass('active');
- $form.find(previewButtonSelector).parent().addClass('active');
+ $form
+ .find(writeButtonSelector)
+ .parent()
+ .removeClass('active');
+ $form
+ .find(previewButtonSelector)
+ .parent()
+ .addClass('active');
// toggle content
$form.find('.md-write-holder').hide();
@@ -158,7 +171,7 @@ $(document).on('markdown-preview:show', function (e, $form) {
markdownPreview.showPreview($form);
});
-$(document).on('markdown-preview:hide', function (e, $form) {
+$(document).on('markdown-preview:hide', function(e, $form) {
if (!$form) {
return;
}
@@ -169,8 +182,14 @@ $(document).on('markdown-preview:hide', function (e, $form) {
}
// toggle tabs
- $form.find(writeButtonSelector).parent().addClass('active');
- $form.find(previewButtonSelector).parent().removeClass('active');
+ $form
+ .find(writeButtonSelector)
+ .parent()
+ .addClass('active');
+ $form
+ .find(previewButtonSelector)
+ .parent()
+ .removeClass('active');
// toggle content
$form.find('.md-write-holder').show();
@@ -181,7 +200,7 @@ $(document).on('markdown-preview:hide', function (e, $form) {
markdownPreview.hideReferencedCommands($form);
});
-$(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
+$(document).on('markdown-preview:toggle', function(e, keyboardEvent) {
var $target;
$target = $(keyboardEvent.target);
if ($target.is('textarea.markdown-area')) {
@@ -194,14 +213,14 @@ $(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
}
});
-$(document).on('click', previewButtonSelector, function (e) {
+$(document).on('click', previewButtonSelector, function(e) {
var $form;
e.preventDefault();
$form = $(this).closest('form');
$(document).triggerHandler('markdown-preview:show', [$form]);
});
-$(document).on('click', writeButtonSelector, function (e) {
+$(document).on('click', writeButtonSelector, function(e) {
var $form;
e.preventDefault();
$form = $(this).closest('form');
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index b6e2781773c..c1ea67f9293 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -28,7 +28,7 @@ function keyCodeIs(e, keyCode) {
return e.keyCode === keyCode;
}
-$(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
+$(document).on('keydown.quick_submit', '.js-quick-submit', e => {
// Enter
if (!keyCodeIs(e, 13)) {
return;
@@ -55,23 +55,25 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
// If the user tabs to a submit button on a `js-quick-submit` form, display a
// tooltip to let them know they could've used the hotkey
-$(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function displayTooltip(e) {
- // Tab
- if (!keyCodeIs(e, 9)) {
- return;
- }
+$(document).on(
+ 'keyup.quick_submit',
+ '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]',
+ function displayTooltip(e) {
+ // Tab
+ if (!keyCodeIs(e, 9)) {
+ return;
+ }
- const $this = $(this);
- const title = isMac() ?
- 'You can also press ⌘-Enter' :
- 'You can also press Ctrl-Enter';
+ const $this = $(this);
+ const title = isMac() ? 'You can also press ⌘-Enter' : 'You can also press Ctrl-Enter';
- $this.tooltip({
- container: 'body',
- html: 'true',
- placement: 'top',
- title,
- trigger: 'manual',
- });
- $this.tooltip('show').one('blur click', () => $this.tooltip('hide'));
-});
+ $this.tooltip({
+ container: 'body',
+ html: 'true',
+ placement: 'top',
+ title,
+ trigger: 'manual',
+ });
+ $this.tooltip('show').one('blur click', () => $this.tooltip('hide'));
+ },
+);
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index a8b6dbf0948..c09d9ccddd6 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -18,7 +18,8 @@ import '../commons/bootstrap';
$.fn.requiresInput = function requiresInput() {
const $form = $(this);
const $button = $('button[type=submit], input[type=submit]', $form);
- const fieldSelector = 'input[required=required], select[required=required], textarea[required=required]';
+ const fieldSelector =
+ 'input[required=required], select[required=required], textarea[required=required]';
function requireInput() {
// Collect the input values of *all* required fields
diff --git a/app/assets/javascripts/behaviors/secret_values.js b/app/assets/javascripts/behaviors/secret_values.js
index 0d6e0dbefcc..f6bf62d734e 100644
--- a/app/assets/javascripts/behaviors/secret_values.js
+++ b/app/assets/javascripts/behaviors/secret_values.js
@@ -32,16 +32,18 @@ export default class SecretValues {
updateDom(isRevealed) {
const values = this.container.querySelectorAll(this.valueSelector);
- values.forEach((value) => {
+ values.forEach(value => {
value.classList.toggle('hide', !isRevealed);
});
const placeholders = this.container.querySelectorAll(this.placeholderSelector);
- placeholders.forEach((placeholder) => {
+ placeholders.forEach(placeholder => {
placeholder.classList.toggle('hide', isRevealed);
});
- this.revealButton.textContent = isRevealed ? n__('Hide value', 'Hide values', values.length) : n__('Reveal value', 'Reveal values', values.length);
+ this.revealButton.textContent = isRevealed
+ ? n__('Hide value', 'Hide values', values.length)
+ : n__('Reveal value', 'Reveal values', values.length);
this.revealButton.dataset.secretRevealStatus = isRevealed;
}
}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index 6719bfd6d22..8b5a3c1c69d 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -88,22 +88,24 @@ export default class Shortcuts {
return null;
}
- return axios.get(gon.shortcuts_path, {
- responseType: 'text',
- }).then(({ data }) => {
- $.globalEval(data);
-
- if (location && location.length > 0) {
- const results = [];
- for (let i = 0, len = location.length; i < len; i += 1) {
- results.push($(location[i]).show());
+ return axios
+ .get(gon.shortcuts_path, {
+ responseType: 'text',
+ })
+ .then(({ data }) => {
+ $.globalEval(data);
+
+ if (location && location.length > 0) {
+ const results = [];
+ for (let i = 0, len = location.length; i < len; i += 1) {
+ results.push($(location[i]).show());
+ }
+ return results;
}
- return results;
- }
- $('.hidden-shortcut').show();
- return $('.js-more-help-button').remove();
- });
+ $('.hidden-shortcut').show();
+ return $('.js-more-help-button').remove();
+ });
}
focusFilter(e) {
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 4446be0e52f..ef8b8788abf 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -18,9 +18,7 @@ $(() => {
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
- $container
- .find('.js-toggle-content')
- .toggle(toggleState);
+ $container.find('.js-toggle-content').toggle(toggleState);
}
$('body').on('click', '.js-toggle-button', function toggleButton(e) {
diff --git a/app/assets/javascripts/blob/3d_viewer/index.js b/app/assets/javascripts/blob/3d_viewer/index.js
index 1bdf1aeb76c..2d4f45cc365 100644
--- a/app/assets/javascripts/blob/3d_viewer/index.js
+++ b/app/assets/javascripts/blob/3d_viewer/index.js
@@ -18,12 +18,7 @@ export default class Renderer {
this.loader = new STLLoader();
this.fov = 45;
- this.camera = new THREE.PerspectiveCamera(
- this.fov,
- this.width / this.height,
- 1,
- 1000,
- );
+ this.camera = new THREE.PerspectiveCamera(this.fov, this.width / this.height, 1, 1000);
this.scene = new THREE.Scene();
@@ -35,10 +30,7 @@ export default class Renderer {
this.setupLight();
// Set up OrbitControls
- this.controls = new OrbitControls(
- this.camera,
- this.renderer.domElement,
- );
+ this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.minDistance = 5;
this.controls.maxDistance = 30;
this.controls.enableKeys = false;
@@ -51,47 +43,32 @@ export default class Renderer {
antialias: true,
});
- this.renderer.setClearColor(0xFFFFFF);
+ this.renderer.setClearColor(0xffffff);
this.renderer.setPixelRatio(window.devicePixelRatio);
- this.renderer.setSize(
- this.width,
- this.height,
- );
+ this.renderer.setSize(this.width, this.height);
}
setupLight() {
// Point light illuminates the object
- const pointLight = new THREE.PointLight(
- 0xFFFFFF,
- 2,
- 0,
- );
+ const pointLight = new THREE.PointLight(0xffffff, 2, 0);
pointLight.castShadow = true;
this.camera.add(pointLight);
// Ambient light illuminates the scene
- const ambientLight = new THREE.AmbientLight(
- 0xFFFFFF,
- 1,
- );
+ const ambientLight = new THREE.AmbientLight(0xffffff, 1);
this.scene.add(ambientLight);
}
setupGrid() {
- this.grid = new THREE.GridHelper(
- 20,
- 20,
- 0x000000,
- 0x000000,
- );
+ this.grid = new THREE.GridHelper(20, 20, 0x000000, 0x000000);
this.scene.add(this.grid);
}
loadFile() {
- this.loader.load(this.container.dataset.endpoint, (geo) => {
+ this.loader.load(this.container.dataset.endpoint, geo => {
const obj = new MeshObject(geo);
this.objects.push(obj);
@@ -116,30 +93,23 @@ export default class Renderer {
}
render() {
- this.renderer.render(
- this.scene,
- this.camera,
- );
+ this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.renderWrapper);
}
changeObjectMaterials(type) {
- this.objects.forEach((obj) => {
+ this.objects.forEach(obj => {
obj.changeMaterial(type);
});
}
setDefaultCameraPosition() {
const obj = this.objects[0];
- const radius = (obj.geometry.boundingSphere.radius / 1.5);
- const dist = radius / (Math.sin((this.fov * (Math.PI / 180)) / 2));
-
- this.camera.position.set(
- 0,
- dist + 1,
- dist,
- );
+ const radius = obj.geometry.boundingSphere.radius / 1.5;
+ const dist = radius / Math.sin((this.fov * (Math.PI / 180)) / 2);
+
+ this.camera.position.set(0, dist + 1, dist);
this.camera.lookAt(this.grid);
this.controls.update();
diff --git a/app/assets/javascripts/blob/3d_viewer/mesh_object.js b/app/assets/javascripts/blob/3d_viewer/mesh_object.js
index 96758884abf..cb7fcff8674 100644
--- a/app/assets/javascripts/blob/3d_viewer/mesh_object.js
+++ b/app/assets/javascripts/blob/3d_viewer/mesh_object.js
@@ -1,10 +1,6 @@
-import {
- Matrix4,
- MeshLambertMaterial,
- Mesh,
-} from 'three/build/three.module';
+import { Matrix4, MeshLambertMaterial, Mesh } from 'three/build/three.module';
-const defaultColor = 0xE24329;
+const defaultColor = 0xe24329;
const materials = {
default: new MeshLambertMaterial({
color: defaultColor,
@@ -17,10 +13,7 @@ const materials = {
export default class MeshObject extends Mesh {
constructor(geo) {
- super(
- geo,
- materials.default,
- );
+ super(geo, materials.default);
this.geometry.computeBoundingSphere();
@@ -29,13 +22,7 @@ export default class MeshObject extends Mesh {
if (this.geometry.boundingSphere.radius > 4) {
const scale = 4 / this.geometry.boundingSphere.radius;
- this.geometry.applyMatrix(
- new Matrix4().makeScale(
- scale,
- scale,
- scale,
- ),
- );
+ this.geometry.applyMatrix(new Matrix4().makeScale(scale, scale, scale));
this.geometry.computeBoundingSphere();
this.position.x = -this.geometry.boundingSphere.center.x;
diff --git a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js
index 7986287f7e7..75777b910ca 100644
--- a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js
+++ b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js
@@ -42,7 +42,7 @@ class BalsamiqViewer {
this.initDatabase(loadEvent.target.response);
const previews = this.getPreviews();
- previews.forEach((preview) => {
+ previews.forEach(preview => {
const renderedPreview = this.renderPreview(preview);
container.appendChild(renderedPreview);
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index ff1739b1679..cd3251ad1ca 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -41,39 +41,45 @@ export default class BlobFileDropzone {
addRemoveLinks: true,
previewsContainer: '.dropzone-previews',
headers: csrf.headers,
- init: function () {
- this.on('addedfile', function () {
+ init: function() {
+ this.on('addedfile', function() {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.addClass(HIDDEN_CLASS);
- $('.dropzone-alerts').html('').hide();
+ $('.dropzone-alerts')
+ .html('')
+ .hide();
});
- this.on('removedfile', function () {
+ this.on('removedfile', function() {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.removeClass(HIDDEN_CLASS);
});
- this.on('success', function (header, response) {
+ this.on('success', function(header, response) {
$('#modal-upload-blob').modal('hide');
visitUrl(response.filePath);
});
- this.on('maxfilesexceeded', function (file) {
+ this.on('maxfilesexceeded', function(file) {
dropzoneMessage.addClass(HIDDEN_CLASS);
this.removeFile(file);
});
- this.on('sending', function (file, xhr, formData) {
+ this.on('sending', function(file, xhr, formData) {
formData.append('branch_name', form.find('.js-branch-name').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val());
});
},
// Override behavior of adding error underneath preview
- error: function (file, errorMessage) {
- const stripped = $('<div/>').html(errorMessage).text();
- $('.dropzone-alerts').html(`Error uploading file: "${stripped}"`).show();
+ error: function(file, errorMessage) {
+ const stripped = $('<div/>')
+ .html(errorMessage)
+ .text();
+ $('.dropzone-alerts')
+ .html(`Error uploading file: "${stripped}"`)
+ .show();
this.removeFile(file);
},
});
- submitButton.on('click', (e) => {
+ submitButton.on('click', e => {
e.preventDefault();
e.stopPropagation();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
diff --git a/app/assets/javascripts/blob/blob_line_permalink_updater.js b/app/assets/javascripts/blob/blob_line_permalink_updater.js
index d36d9f0de2d..62f0a56ed75 100644
--- a/app/assets/javascripts/blob/blob_line_permalink_updater.js
+++ b/app/assets/javascripts/blob/blob_line_permalink_updater.js
@@ -2,17 +2,19 @@ import { getLocationHash } from '../lib/utils/url_utility';
const lineNumberRe = /^L[0-9]+/;
-const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
+const updateLineNumbersOnBlobPermalinks = linksToUpdate => {
const hash = getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;
- [].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => {
- const baseHref = permalinkButton.getAttribute('data-original-href') || (() => {
- const href = permalinkButton.getAttribute('href');
- permalinkButton.setAttribute('data-original-href', href);
- return href;
- })();
+ [].concat(Array.prototype.slice.call(linksToUpdate)).forEach(permalinkButton => {
+ const baseHref =
+ permalinkButton.getAttribute('data-original-href') ||
+ (() => {
+ const href = permalinkButton.getAttribute('href');
+ permalinkButton.setAttribute('data-original-href', href);
+ return href;
+ })();
permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
});
}
@@ -26,7 +28,7 @@ function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, element
}, 0);
};
- blobContentHolder.addEventListener('click', (e) => {
+ blobContentHolder.addEventListener('click', e => {
if (e.target.matches(lineNumberSelector)) {
updateBlameAndBlobPermalinkCb();
}
diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js
index 02228434a29..476901aae75 100644
--- a/app/assets/javascripts/blob/file_template_selector.js
+++ b/app/assets/javascripts/blob/file_template_selector.js
@@ -45,15 +45,11 @@ export default class FileTemplateSelector {
}
renderLoading() {
- this.$loadingIcon
- .addClass('fa-spinner fa-spin')
- .removeClass('fa-chevron-down');
+ this.$loadingIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
}
renderLoaded() {
- this.$loadingIcon
- .addClass('fa-chevron-down')
- .removeClass('fa-spinner fa-spin');
+ this.$loadingIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
}
reportSelection(options) {
diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js
index 6f1350e80fc..071022a9a75 100644
--- a/app/assets/javascripts/blob/notebook/index.js
+++ b/app/assets/javascripts/blob/notebook/index.js
@@ -40,13 +40,14 @@ export default () => {
},
methods: {
loadFile() {
- axios.get(el.dataset.endpoint)
+ axios
+ .get(el.dataset.endpoint)
.then(res => res.data)
- .then((data) => {
+ .then(data => {
this.json = data;
this.loading = false;
})
- .catch((e) => {
+ .catch(e => {
if (e.status !== 200) {
this.loadError = true;
}
diff --git a/app/assets/javascripts/blob/sketch/index.js b/app/assets/javascripts/blob/sketch/index.js
index 13318c58006..57c1baa9886 100644
--- a/app/assets/javascripts/blob/sketch/index.js
+++ b/app/assets/javascripts/blob/sketch/index.js
@@ -13,7 +13,7 @@ export default class SketchLoader {
return this.getZipFile()
.then(data => JSZip.loadAsync(data))
.then(asyncResult => asyncResult.files['previews/preview.png'].async('uint8array'))
- .then((content) => {
+ .then(content => {
const url = window.URL || window.webkitURL;
const blob = new Blob([new Uint8Array(content)], {
type: 'image/png',
diff --git a/app/assets/javascripts/blob/stl_viewer.js b/app/assets/javascripts/blob/stl_viewer.js
index 339906adc34..f129b6e631e 100644
--- a/app/assets/javascripts/blob/stl_viewer.js
+++ b/app/assets/javascripts/blob/stl_viewer.js
@@ -3,8 +3,8 @@ import Renderer from './3d_viewer';
export default () => {
const viewer = new Renderer(document.getElementById('js-stl-viewer'));
- [].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
- el.addEventListener('click', (e) => {
+ [].slice.call(document.querySelectorAll('.js-material-changer')).forEach(el => {
+ el.addEventListener('click', e => {
const { target } = e;
e.preventDefault();
diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js
index 9db1fa70ffb..37e348d93d3 100644
--- a/app/assets/javascripts/blob/template_selector.js
+++ b/app/assets/javascripts/blob/template_selector.js
@@ -81,14 +81,10 @@ export default class TemplateSelector {
}
startLoadingSpinner() {
- this.$dropdownIcon
- .addClass('fa-spinner fa-spin')
- .removeClass('fa-chevron-down');
+ this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
}
stopLoadingSpinner() {
- this.$dropdownIcon
- .addClass('fa-chevron-down')
- .removeClass('fa-spinner fa-spin');
+ this.$dropdownIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
}
}
diff --git a/app/assets/javascripts/blob/template_selectors/license_selector.js b/app/assets/javascripts/blob/template_selectors/license_selector.js
index ac1fe95eee5..d01ab9257d6 100644
--- a/app/assets/javascripts/blob/template_selectors/license_selector.js
+++ b/app/assets/javascripts/blob/template_selectors/license_selector.js
@@ -22,7 +22,7 @@ export default class BlobLicenseSelector extends FileTemplateSelector {
search: {
fields: ['name'],
},
- clicked: (options) => {
+ clicked: options => {
const { e } = options;
const el = options.$el;
const query = options.selectedObj;
diff --git a/app/assets/javascripts/blob/template_selectors/type_selector.js b/app/assets/javascripts/blob/template_selectors/type_selector.js
index a09381014a7..db3c144cbe3 100644
--- a/app/assets/javascripts/blob/template_selectors/type_selector.js
+++ b/app/assets/javascripts/blob/template_selectors/type_selector.js
@@ -21,5 +21,4 @@ export default class FileTemplateTypeSelector extends FileTemplateSelector {
text: item => item.name,
});
}
-
}
diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js
index 5485248cfaf..befa1dc455f 100644
--- a/app/assets/javascripts/blob/viewer/index.js
+++ b/app/assets/javascripts/blob/viewer/index.js
@@ -22,9 +22,8 @@ export default class BlobViewer {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
if (!viewer || !viewer.dataset.richType) return;
- const initViewer = promise => promise
- .then(module => module.default(viewer))
- .catch((error) => {
+ const initViewer = promise =>
+ promise.then(module => module.default(viewer)).catch(error => {
Flash('Error loading file viewer.');
throw error;
});
@@ -79,10 +78,9 @@ export default class BlobViewer {
initBindings() {
if (this.switcherBtns.length) {
- Array.from(this.switcherBtns)
- .forEach((el) => {
- el.addEventListener('click', this.switchViewHandler.bind(this));
- });
+ Array.from(this.switcherBtns).forEach(el => {
+ el.addEventListener('click', this.switchViewHandler.bind(this));
+ });
}
if (this.copySourceBtn) {
@@ -109,7 +107,10 @@ export default class BlobViewer {
this.copySourceBtn.setAttribute('title', 'Copy source to clipboard');
this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) {
- this.copySourceBtn.setAttribute('title', 'Wait for the source to load to copy it to the clipboard');
+ this.copySourceBtn.setAttribute(
+ 'title',
+ 'Wait for the source to load to copy it to the clipboard',
+ );
this.copySourceBtn.classList.add('disabled');
} else {
this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard');
@@ -147,15 +148,15 @@ export default class BlobViewer {
this.toggleCopyButtonState();
BlobViewer.loadViewer(newViewer)
- .then((viewer) => {
- $(viewer).renderGFM();
+ .then(viewer => {
+ $(viewer).renderGFM();
- this.$fileHolder.trigger('highlight:line');
- handleLocationHash();
+ this.$fileHolder.trigger('highlight:line');
+ handleLocationHash();
- this.toggleCopyButtonState();
- })
- .catch(() => new Flash('Error loading viewer'));
+ this.toggleCopyButtonState();
+ })
+ .catch(() => new Flash('Error loading viewer'));
}
static loadViewer(viewerParam) {
@@ -168,12 +169,11 @@ export default class BlobViewer {
viewer.setAttribute('data-loading', 'true');
- return axios.get(url)
- .then(({ data }) => {
- viewer.innerHTML = data.html;
- viewer.setAttribute('data-loaded', 'true');
+ return axios.get(url).then(({ data }) => {
+ viewer.innerHTML = data.html;
+ viewer.setAttribute('data-loaded', 'true');
- return viewer;
- });
+ return viewer;
+ });
}
}
diff --git a/app/assets/javascripts/breadcrumb.js b/app/assets/javascripts/breadcrumb.js
index 1474d93dde6..a37838694ec 100644
--- a/app/assets/javascripts/breadcrumb.js
+++ b/app/assets/javascripts/breadcrumb.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
-export const addTooltipToEl = (el) => {
+export const addTooltipToEl = el => {
const textEl = el.querySelector('.js-breadcrumb-item-text');
if (textEl && textEl.scrollWidth > textEl.offsetWidth) {
@@ -14,17 +14,18 @@ export default () => {
const breadcrumbs = document.querySelector('.js-breadcrumbs-list');
if (breadcrumbs) {
- const topLevelLinks = [...breadcrumbs.children].filter(el => !el.classList.contains('dropdown'))
+ const topLevelLinks = [...breadcrumbs.children]
+ .filter(el => !el.classList.contains('dropdown'))
.map(el => el.querySelector('a'))
.filter(el => el);
const $expander = $('.js-breadcrumbs-collapsed-expander');
topLevelLinks.forEach(el => addTooltipToEl(el));
- $expander.closest('.dropdown')
- .on('show.bs.dropdown hide.bs.dropdown', (e) => {
- $('.js-breadcrumbs-collapsed-expander', e.currentTarget).toggleClass('open')
- .tooltip('hide');
- });
+ $expander.closest('.dropdown').on('show.bs.dropdown hide.bs.dropdown', e => {
+ $('.js-breadcrumbs-collapsed-expander', e.currentTarget)
+ .toggleClass('open')
+ .tooltip('hide');
+ });
}
};
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
index e338376fcaa..97a1645aa51 100644
--- a/app/assets/javascripts/build_artifacts.js
+++ b/app/assets/javascripts/build_artifacts.js
@@ -12,16 +12,16 @@ export default class BuildArtifacts {
}
// eslint-disable-next-line class-methods-use-this
disablePropagation() {
- $('.top-block').on('click', '.download', function (e) {
+ $('.top-block').on('click', '.download', function(e) {
return e.stopPropagation();
});
- return $('.tree-holder').on('click', 'tr[data-link] a', function (e) {
+ return $('.tree-holder').on('click', 'tr[data-link] a', function(e) {
return e.stopImmediatePropagation();
});
}
// eslint-disable-next-line class-methods-use-this
setupEntryClick() {
- return $('.tree-holder').on('click', 'tr[data-link]', function () {
+ return $('.tree-holder').on('click', 'tr[data-link]', function() {
visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink));
});
}
@@ -37,11 +37,15 @@ export default class BuildArtifacts {
// We want the tooltip to show if you hover anywhere on the row
// But be placed below and in the middle of the file name
$('.js-artifact-tree-row')
- .on('mouseenter', (e) => {
- $(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('show');
+ .on('mouseenter', e => {
+ $(e.currentTarget)
+ .find('.js-artifact-tree-tooltip')
+ .tooltip('show');
})
- .on('mouseleave', (e) => {
- $(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('hide');
+ .on('mouseleave', e => {
+ $(e.currentTarget)
+ .find('.js-artifact-tree-tooltip')
+ .tooltip('hide');
});
}
}
diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
index b33adff609f..1089d0a72d3 100644
--- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js
@@ -7,11 +7,13 @@ import statusCodes from '../lib/utils/http_status';
import VariableList from './ci_variable_list';
function generateErrorBoxContent(errors) {
- const errorList = [].concat(errors).map(errorString => `
+ const errorList = [].concat(errors).map(
+ errorString => `
<li>
${_.escape(errorString)}
</li>
- `);
+ `,
+ );
return `
<p>
@@ -25,13 +27,7 @@ function generateErrorBoxContent(errors) {
// Used for the variable list on CI/CD projects/groups settings page
export default class AjaxVariableList {
- constructor({
- container,
- saveButton,
- errorBox,
- formField = 'variables',
- saveEndpoint,
- }) {
+ constructor({ container, saveButton, errorBox, formField = 'variables', saveEndpoint }) {
this.container = container;
this.saveButton = saveButton;
this.errorBox = errorBox;
@@ -58,18 +54,21 @@ export default class AjaxVariableList {
// to match it up in `updateRowsWithPersistedVariables`
this.variableList.toggleEnableRow(false);
- return axios.patch(this.saveEndpoint, {
- variables_attributes: this.variableList.getAllData(),
- }, {
- // We want to be able to process the `res.data` from a 400 error response
- // and print the validation messages such as duplicate variable keys
- validateStatus: status => (
- status >= statusCodes.OK &&
- status < statusCodes.MULTIPLE_CHOICES
- ) ||
- status === statusCodes.BAD_REQUEST,
- })
- .then((res) => {
+ return axios
+ .patch(
+ this.saveEndpoint,
+ {
+ variables_attributes: this.variableList.getAllData(),
+ },
+ {
+ // We want to be able to process the `res.data` from a 400 error response
+ // and print the validation messages such as duplicate variable keys
+ validateStatus: status =>
+ (status >= statusCodes.OK && status < statusCodes.MULTIPLE_CHOICES) ||
+ status === statusCodes.BAD_REQUEST,
+ },
+ )
+ .then(res => {
loadingIcon.classList.toggle('hide', true);
this.variableList.toggleEnableRow(true);
@@ -90,18 +89,21 @@ export default class AjaxVariableList {
}
updateRowsWithPersistedVariables(persistedVariables = []) {
- const persistedVariableMap = [].concat(persistedVariables).reduce((variableMap, variable) => ({
- ...variableMap,
- [variable.key]: variable,
- }), {});
+ const persistedVariableMap = [].concat(persistedVariables).reduce(
+ (variableMap, variable) => ({
+ ...variableMap,
+ [variable.key]: variable,
+ }),
+ {},
+ );
- this.container.querySelectorAll('.js-row').forEach((row) => {
+ this.container.querySelectorAll('.js-row').forEach(row => {
// If we submitted a row that was destroyed, remove it so we don't try
// to destroy it again which would cause a BE error
const destroyInput = row.querySelector('.js-ci-variable-input-destroy');
if (convertPermissionToBoolean(destroyInput.value)) {
row.remove();
- // Update the ID input so any future edits and `_destroy` will apply on the BE
+ // Update the ID input so any future edits and `_destroy` will apply on the BE
} else {
const key = row.querySelector('.js-ci-variable-input-key').value;
const persistedVariable = persistedVariableMap[key];
diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
index 47efb3a8cee..7bdc18ce03e 100644
--- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js
@@ -16,10 +16,7 @@ function createEnvironmentItem(value) {
}
export default class VariableList {
- constructor({
- container,
- formField,
- }) {
+ constructor({ container, formField }) {
this.$container = $(container);
this.formField = formField;
this.environmentDropdownMap = new WeakMap();
@@ -71,7 +68,7 @@ export default class VariableList {
this.initRow(rowEl);
});
- this.$container.on('click', '.js-row-remove-button', (e) => {
+ this.$container.on('click', '.js-row-remove-button', e => {
e.preventDefault();
this.removeRow($(e.currentTarget).closest('.js-row'));
});
@@ -81,7 +78,7 @@ export default class VariableList {
.join(',');
// Remove any empty rows except the last row
- this.$container.on('blur', inputSelector, (e) => {
+ this.$container.on('blur', inputSelector, e => {
const $row = $(e.currentTarget).closest('.js-row');
if ($row.is(':not(:last-child)') && !this.checkIfRowTouched($row)) {
@@ -136,7 +133,7 @@ export default class VariableList {
$rowClone.removeAttr('data-is-persisted');
// Reset the inputs to their defaults
- Object.keys(this.inputMap).forEach((name) => {
+ Object.keys(this.inputMap).forEach(name => {
const entry = this.inputMap[name];
$rowClone.find(entry.selector).val(entry.default);
});
@@ -171,7 +168,7 @@ export default class VariableList {
}
checkIfRowTouched($row) {
- return Object.keys(this.inputMap).some((name) => {
+ return Object.keys(this.inputMap).some(name => {
const entry = this.inputMap[name];
const $el = $row.find(entry.selector);
return $el.length && $el.val() !== entry.default;
@@ -190,11 +187,14 @@ export default class VariableList {
getAllData() {
// Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems.
- const validRows = this.$container.find('.js-row').toArray().slice(0, -1);
+ const validRows = this.$container
+ .find('.js-row')
+ .toArray()
+ .slice(0, -1);
- return validRows.map((rowEl) => {
+ return validRows.map(rowEl => {
const resultant = {};
- Object.keys(this.inputMap).forEach((name) => {
+ Object.keys(this.inputMap).forEach(name => {
const entry = this.inputMap[name];
const $input = $(rowEl).find(entry.selector);
if ($input.length) {
@@ -207,11 +207,16 @@ export default class VariableList {
}
getEnvironmentValues() {
- const valueMap = this.$container.find(this.inputMap.environment_scope.selector).toArray()
- .reduce((prevValueMap, envInput) => ({
- ...prevValueMap,
- [envInput.value]: envInput.value,
- }), {});
+ const valueMap = this.$container
+ .find(this.inputMap.environment_scope.selector)
+ .toArray()
+ .reduce(
+ (prevValueMap, envInput) => ({
+ ...prevValueMap,
+ [envInput.value]: envInput.value,
+ }),
+ {},
+ );
return Object.keys(valueMap).map(createEnvironmentItem);
}
diff --git a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
index 7cd5916ac9c..e7111c666a2 100644
--- a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
+++ b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js
@@ -2,10 +2,7 @@ import $ from 'jquery';
import VariableList from './ci_variable_list';
// Used for the variable list on scheduled pipeline edit page
-export default function setupNativeFormVariableList({
- container,
- formField = 'variables',
-}) {
+export default function setupNativeFormVariableList({ container, formField = 'variables' }) {
const $container = $(container);
const variableList = new VariableList({
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 0452729d3ff..236bb1394c8 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -1,153 +1,162 @@
<script>
- /* eslint-disable vue/require-default-prop */
- import { s__, sprintf } from '../../locale';
- import eventHub from '../event_hub';
- import identicon from '../../vue_shared/components/identicon.vue';
- import loadingButton from '../../vue_shared/components/loading_button.vue';
- import {
- APPLICATION_STATUS,
- REQUEST_LOADING,
- REQUEST_SUCCESS,
- REQUEST_FAILURE,
- } from '../constants';
+/* eslint-disable vue/require-default-prop */
+import { s__, sprintf } from '../../locale';
+import eventHub from '../event_hub';
+import identicon from '../../vue_shared/components/identicon.vue';
+import loadingButton from '../../vue_shared/components/loading_button.vue';
+import {
+ APPLICATION_STATUS,
+ REQUEST_LOADING,
+ REQUEST_SUCCESS,
+ REQUEST_FAILURE,
+} from '../constants';
- export default {
- components: {
- loadingButton,
- identicon,
+export default {
+ components: {
+ loadingButton,
+ identicon,
+ },
+ props: {
+ id: {
+ type: String,
+ required: true,
},
- props: {
- id: {
- type: String,
- required: true,
- },
- title: {
- type: String,
- required: true,
- },
- titleLink: {
- type: String,
- required: false,
- },
- manageLink: {
- type: String,
- required: false,
- },
- logoUrl: {
- type: String,
- required: false,
- default: null,
- },
- disabled: {
- type: Boolean,
- required: false,
- default: false,
- },
- status: {
- type: String,
- required: false,
- },
- statusReason: {
- type: String,
- required: false,
- },
- requestStatus: {
- type: String,
- required: false,
- },
- requestReason: {
- type: String,
- required: false,
- },
- installApplicationRequestParams: {
- type: Object,
- required: false,
- default: () => ({}),
- },
+ title: {
+ type: String,
+ required: true,
},
- computed: {
- isUnknownStatus() {
- return !this.isKnownStatus && this.status !== null;
- },
- isKnownStatus() {
- return Object.values(APPLICATION_STATUS).includes(this.status);
- },
- isInstalled() {
- return (
- this.status === APPLICATION_STATUS.INSTALLED || this.status === APPLICATION_STATUS.UPDATED
- );
- },
- hasLogo() {
- return !!this.logoUrl;
- },
- identiconId() {
- // generate a deterministic integer id for the identicon background
- return this.id.charCodeAt(0);
- },
- rowJsClass() {
- return `js-cluster-application-row-${this.id}`;
- },
- installButtonLoading() {
- return !this.status ||
- this.status === APPLICATION_STATUS.SCHEDULED ||
- this.status === APPLICATION_STATUS.INSTALLING ||
- this.requestStatus === REQUEST_LOADING;
- },
- installButtonDisabled() {
- // Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but
- // we already made a request to install and are just waiting for the real-time
- // to sync up.
- return ((this.status !== APPLICATION_STATUS.INSTALLABLE
- && this.status !== APPLICATION_STATUS.ERROR) ||
+ titleLink: {
+ type: String,
+ required: false,
+ },
+ manageLink: {
+ type: String,
+ required: false,
+ },
+ logoUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ status: {
+ type: String,
+ required: false,
+ },
+ statusReason: {
+ type: String,
+ required: false,
+ },
+ requestStatus: {
+ type: String,
+ required: false,
+ },
+ requestReason: {
+ type: String,
+ required: false,
+ },
+ installApplicationRequestParams: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ },
+ computed: {
+ isUnknownStatus() {
+ return !this.isKnownStatus && this.status !== null;
+ },
+ isKnownStatus() {
+ return Object.values(APPLICATION_STATUS).includes(this.status);
+ },
+ isInstalled() {
+ return (
+ this.status === APPLICATION_STATUS.INSTALLED ||
+ this.status === APPLICATION_STATUS.UPDATED ||
+ this.status === APPLICATION_STATUS.UPDATING
+ );
+ },
+ hasLogo() {
+ return !!this.logoUrl;
+ },
+ identiconId() {
+ // generate a deterministic integer id for the identicon background
+ return this.id.charCodeAt(0);
+ },
+ rowJsClass() {
+ return `js-cluster-application-row-${this.id}`;
+ },
+ installButtonLoading() {
+ return (
+ !this.status ||
+ this.status === APPLICATION_STATUS.SCHEDULED ||
+ this.status === APPLICATION_STATUS.INSTALLING ||
+ this.requestStatus === REQUEST_LOADING
+ );
+ },
+ installButtonDisabled() {
+ // Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but
+ // we already made a request to install and are just waiting for the real-time
+ // to sync up.
+ return (
+ ((this.status !== APPLICATION_STATUS.INSTALLABLE &&
+ this.status !== APPLICATION_STATUS.ERROR) ||
this.requestStatus === REQUEST_LOADING ||
- this.requestStatus === REQUEST_SUCCESS) && this.isKnownStatus;
- },
- installButtonLabel() {
- let label;
- if (
- this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
- this.status === APPLICATION_STATUS.INSTALLABLE ||
- this.status === APPLICATION_STATUS.ERROR ||
- this.isUnknownStatus
- ) {
- label = s__('ClusterIntegration|Install');
- } else if (this.status === APPLICATION_STATUS.SCHEDULED ||
- this.status === APPLICATION_STATUS.INSTALLING) {
- label = s__('ClusterIntegration|Installing');
- } else if (this.status === APPLICATION_STATUS.INSTALLED ||
- this.status === APPLICATION_STATUS.UPDATED) {
- label = s__('ClusterIntegration|Installed');
- }
+ this.requestStatus === REQUEST_SUCCESS) &&
+ this.isKnownStatus
+ );
+ },
+ installButtonLabel() {
+ let label;
+ if (
+ this.status === APPLICATION_STATUS.NOT_INSTALLABLE ||
+ this.status === APPLICATION_STATUS.INSTALLABLE ||
+ this.status === APPLICATION_STATUS.ERROR ||
+ this.isUnknownStatus
+ ) {
+ label = s__('ClusterIntegration|Install');
+ } else if (
+ this.status === APPLICATION_STATUS.SCHEDULED ||
+ this.status === APPLICATION_STATUS.INSTALLING
+ ) {
+ label = s__('ClusterIntegration|Installing');
+ } else if (
+ this.status === APPLICATION_STATUS.INSTALLED ||
+ this.status === APPLICATION_STATUS.UPDATED ||
+ this.status === APPLICATION_STATUS.UPDATING
+ ) {
+ label = s__('ClusterIntegration|Installed');
+ }
- return label;
- },
- showManageButton() {
- return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
- },
- manageButtonLabel() {
- return s__('ClusterIntegration|Manage');
- },
- hasError() {
- return this.status === APPLICATION_STATUS.ERROR ||
- this.requestStatus === REQUEST_FAILURE;
- },
- generalErrorDescription() {
- return sprintf(
- s__('ClusterIntegration|Something went wrong while installing %{title}'), {
- title: this.title,
- },
- );
- },
+ return label;
+ },
+ showManageButton() {
+ return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED;
+ },
+ manageButtonLabel() {
+ return s__('ClusterIntegration|Manage');
+ },
+ hasError() {
+ return this.status === APPLICATION_STATUS.ERROR || this.requestStatus === REQUEST_FAILURE;
+ },
+ generalErrorDescription() {
+ return sprintf(s__('ClusterIntegration|Something went wrong while installing %{title}'), {
+ title: this.title,
+ });
},
- methods: {
- installClicked() {
- eventHub.$emit('installApplication', {
- id: this.id,
- params: this.installApplicationRequestParams,
- });
- },
+ },
+ methods: {
+ installClicked() {
+ eventHub.$emit('installApplication', {
+ id: this.id,
+ params: this.installApplicationRequestParams,
+ });
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index 72fc9355d82..24a49624583 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -6,6 +6,7 @@ export const APPLICATION_STATUS = {
INSTALLING: 'installing',
INSTALLED: 'installed',
UPDATED: 'updated',
+ UPDATING: 'updating',
ERROR: 'errored',
};
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index d90db7b103c..106ac3cb516 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -76,12 +76,8 @@ export default class ClusterStore {
this.state.status = serverState.status;
this.state.statusReason = serverState.status_reason;
- serverState.applications.forEach((serverAppEntry) => {
- const {
- name: appId,
- status,
- status_reason: statusReason,
- } = serverAppEntry;
+ serverState.applications.forEach(serverAppEntry => {
+ const { name: appId, status, status_reason: statusReason } = serverAppEntry;
this.state.applications[appId] = {
...(this.state.applications[appId] || {}),
diff --git a/app/assets/javascripts/comment_type_toggle.js b/app/assets/javascripts/comment_type_toggle.js
index c74184949df..a259667bb75 100644
--- a/app/assets/javascripts/comment_type_toggle.js
+++ b/app/assets/javascripts/comment_type_toggle.js
@@ -24,36 +24,44 @@ class CommentTypeToggle {
setConfig() {
const config = {
- InputSetter: [{
- input: this.noteTypeInput,
- valueAttribute: 'data-value',
- },
- {
- input: this.submitButton,
- valueAttribute: 'data-submit-text',
- }],
+ InputSetter: [
+ {
+ input: this.noteTypeInput,
+ valueAttribute: 'data-value',
+ },
+ {
+ input: this.submitButton,
+ valueAttribute: 'data-submit-text',
+ },
+ ],
};
if (this.closeButton) {
- config.InputSetter.push({
- input: this.closeButton,
- valueAttribute: 'data-close-text',
- }, {
- input: this.closeButton,
- valueAttribute: 'data-close-text',
- inputAttribute: 'data-alternative-text',
- });
+ config.InputSetter.push(
+ {
+ input: this.closeButton,
+ valueAttribute: 'data-close-text',
+ },
+ {
+ input: this.closeButton,
+ valueAttribute: 'data-close-text',
+ inputAttribute: 'data-alternative-text',
+ },
+ );
}
if (this.reopenButton) {
- config.InputSetter.push({
- input: this.reopenButton,
- valueAttribute: 'data-reopen-text',
- }, {
- input: this.reopenButton,
- valueAttribute: 'data-reopen-text',
- inputAttribute: 'data-alternative-text',
- });
+ config.InputSetter.push(
+ {
+ input: this.reopenButton,
+ valueAttribute: 'data-reopen-text',
+ },
+ {
+ input: this.reopenButton,
+ valueAttribute: 'data-reopen-text',
+ inputAttribute: 'data-alternative-text',
+ },
+ );
}
return config;
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index 30d9b656fec..d4ecfa4aa93 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -9,44 +9,60 @@ const viewModes = ['two-up', 'swipe'];
export default class ImageFile {
constructor(file) {
this.file = file;
- this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
- return function(deletedWidth, deletedHeight) {
- return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
- _this.initViewModes();
-
- // Load two-up view after images are loaded
- // so that we can display the correct width and height information
- const $images = $('.two-up.view img', _this.file);
-
- $images.waitForImages(function() {
- _this.initView('two-up');
+ this.requestImageInfo(
+ $('.two-up.view .frame.deleted img', this.file),
+ (function(_this) {
+ return function(deletedWidth, deletedHeight) {
+ return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(
+ width,
+ height,
+ ) {
+ _this.initViewModes();
+
+ // Load two-up view after images are loaded
+ // so that we can display the correct width and height information
+ const $images = $('.two-up.view img', _this.file);
+
+ $images.waitForImages(function() {
+ _this.initView('two-up');
+ });
});
- });
- };
- })(this));
+ };
+ })(this),
+ );
}
initViewModes() {
const viewMode = viewModes[0];
$('.view-modes', this.file).removeClass('hide');
- $('.view-modes-menu', this.file).on('click', 'li', (function(_this) {
- return function(event) {
- if (!$(event.currentTarget).hasClass('active')) {
- return _this.activateViewMode(event.currentTarget.className);
- }
- };
- })(this));
+ $('.view-modes-menu', this.file).on(
+ 'click',
+ 'li',
+ (function(_this) {
+ return function(event) {
+ if (!$(event.currentTarget).hasClass('active')) {
+ return _this.activateViewMode(event.currentTarget.className);
+ }
+ };
+ })(this),
+ );
return this.activateViewMode(viewMode);
}
activateViewMode(viewMode) {
- $('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active');
- return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) {
- return function() {
- $(".view." + viewMode, _this.file).fadeIn(200);
- return _this.initView(viewMode);
- };
- })(this));
+ $('.view-modes-menu li', this.file)
+ .removeClass('active')
+ .filter('.' + viewMode)
+ .addClass('active');
+ return $('.view:visible:not(.' + viewMode + ')', this.file).fadeOut(
+ 200,
+ (function(_this) {
+ return function() {
+ $('.view.' + viewMode, _this.file).fadeIn(200);
+ return _this.initView(viewMode);
+ };
+ })(this),
+ );
}
initView(viewMode) {
@@ -63,135 +79,154 @@ export default class ImageFile {
$body.css('user-select', 'none');
});
- $body.off('mouseup').off('mousemove').on('mouseup', function() {
- dragging = false;
- $body.css('user-select', '');
- })
- .on('mousemove', function(e) {
- var left;
- if (!dragging) return;
-
- left = e.pageX - ($offsetEl.offset().left + padding);
-
- callback(e, left);
- });
+ $body
+ .off('mouseup')
+ .off('mousemove')
+ .on('mouseup', function() {
+ dragging = false;
+ $body.css('user-select', '');
+ })
+ .on('mousemove', function(e) {
+ var left;
+ if (!dragging) return;
+
+ left = e.pageX - ($offsetEl.offset().left + padding);
+
+ callback(e, left);
+ });
}
prepareFrames(view) {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
- $('.frame', view).each((function(_this) {
- return function(index, frame) {
- var height, width;
- width = $(frame).width();
- height = $(frame).height();
- maxWidth = width > maxWidth ? width : maxWidth;
- return maxHeight = height > maxHeight ? height : maxHeight;
- };
- })(this)).css({
- width: maxWidth,
- height: maxHeight
- });
+ $('.frame', view)
+ .each(
+ (function(_this) {
+ return function(index, frame) {
+ var height, width;
+ width = $(frame).width();
+ height = $(frame).height();
+ maxWidth = width > maxWidth ? width : maxWidth;
+ return (maxHeight = height > maxHeight ? height : maxHeight);
+ };
+ })(this),
+ )
+ .css({
+ width: maxWidth,
+ height: maxHeight,
+ });
return [maxWidth, maxHeight];
}
views = {
'two-up': function() {
- return $('.two-up.view .wrap', this.file).each((function(_this) {
- return function(index, wrap) {
- $('img', wrap).each(function() {
- var currentWidth;
- currentWidth = $(this).width();
- if (currentWidth > availWidth / 2) {
- return $(this).width(availWidth / 2);
- }
- });
- return _this.requestImageInfo($('img', wrap), function(width, height) {
- $('.image-info .meta-width', wrap).text(width + "px");
- $('.image-info .meta-height', wrap).text(height + "px");
- return $('.image-info', wrap).removeClass('hide');
- });
- };
- })(this));
+ return $('.two-up.view .wrap', this.file).each(
+ (function(_this) {
+ return function(index, wrap) {
+ $('img', wrap).each(function() {
+ var currentWidth;
+ currentWidth = $(this).width();
+ if (currentWidth > availWidth / 2) {
+ return $(this).width(availWidth / 2);
+ }
+ });
+ return _this.requestImageInfo($('img', wrap), function(width, height) {
+ $('.image-info .meta-width', wrap).text(width + 'px');
+ $('.image-info .meta-height', wrap).text(height + 'px');
+ return $('.image-info', wrap).removeClass('hide');
+ });
+ };
+ })(this),
+ );
},
- 'swipe': function() {
+ swipe() {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
- return $('.swipe.view', this.file).each((function(_this) {
- return function(index, view) {
- var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
- ref = _this.prepareFrames(view), [maxWidth, maxHeight] = ref;
- $swipeFrame = $('.swipe-frame', view);
- $swipeWrap = $('.swipe-wrap', view);
- $swipeBar = $('.swipe-bar', view);
-
- $swipeFrame.css({
- width: maxWidth + 16,
- height: maxHeight + 28
- });
- $swipeWrap.css({
- width: maxWidth + 1,
- height: maxHeight + 2
- });
- // Set swipeBar left position to match image frame
- $swipeBar.css({
- left: 1
- });
-
- wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
-
- _this.initDraggable($swipeBar, wrapPadding, function(e, left) {
- if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) {
- $swipeWrap.width((maxWidth + 1) - left);
- $swipeBar.css('left', left);
- }
- });
- };
- })(this));
+ return $('.swipe.view', this.file).each(
+ (function(_this) {
+ return function(index, view) {
+ var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
+ (ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
+ $swipeFrame = $('.swipe-frame', view);
+ $swipeWrap = $('.swipe-wrap', view);
+ $swipeBar = $('.swipe-bar', view);
+
+ $swipeFrame.css({
+ width: maxWidth + 16,
+ height: maxHeight + 28,
+ });
+ $swipeWrap.css({
+ width: maxWidth + 1,
+ height: maxHeight + 2,
+ });
+ // Set swipeBar left position to match image frame
+ $swipeBar.css({
+ left: 1,
+ });
+
+ wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
+
+ _this.initDraggable($swipeBar, wrapPadding, function(e, left) {
+ if (left > 0 && left < $swipeFrame.width() - wrapPadding * 2) {
+ $swipeWrap.width(maxWidth + 1 - left);
+ $swipeBar.css('left', left);
+ }
+ });
+ };
+ })(this),
+ );
},
'onion-skin': function() {
var dragTrackWidth, maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
- return $('.onion-skin.view', this.file).each((function(_this) {
- return function(index, view) {
- var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
- ref = _this.prepareFrames(view), [maxWidth, maxHeight] = ref;
- $frame = $('.onion-skin-frame', view);
- $frameAdded = $('.frame.added', view);
- $track = $('.drag-track', view);
- $dragger = $('.dragger', $track);
-
- $frame.css({
- width: maxWidth + 16,
- height: maxHeight + 28
- });
- $('.swipe-wrap', view).css({
- width: maxWidth + 1,
- height: maxHeight + 2
- });
- $dragger.css({
- left: dragTrackWidth
- });
-
- $frameAdded.css('opacity', 1);
- framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
-
- _this.initDraggable($dragger, framePadding, function(e, left) {
- var opacity = left / dragTrackWidth;
-
- if (opacity >= 0 && opacity <= 1) {
- $dragger.css('left', left);
- $frameAdded.css('opacity', opacity);
- }
- });
- };
- })(this));
- }
- }
+ return $('.onion-skin.view', this.file).each(
+ (function(_this) {
+ return function(index, view) {
+ var $frame,
+ $track,
+ $dragger,
+ $frameAdded,
+ framePadding,
+ ref,
+ dragging = false;
+ (ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
+ $frame = $('.onion-skin-frame', view);
+ $frameAdded = $('.frame.added', view);
+ $track = $('.drag-track', view);
+ $dragger = $('.dragger', $track);
+
+ $frame.css({
+ width: maxWidth + 16,
+ height: maxHeight + 28,
+ });
+ $('.swipe-wrap', view).css({
+ width: maxWidth + 1,
+ height: maxHeight + 2,
+ });
+ $dragger.css({
+ left: dragTrackWidth,
+ });
+
+ $frameAdded.css('opacity', 1);
+ framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
+
+ _this.initDraggable($dragger, framePadding, function(e, left) {
+ var opacity = left / dragTrackWidth;
+
+ if (opacity >= 0 && opacity <= 1) {
+ $dragger.css('left', left);
+ $frameAdded.css('opacity', opacity);
+ }
+ });
+ };
+ })(this),
+ );
+ },
+ };
requestImageInfo(img, callback) {
const domImg = img.get(0);
@@ -199,11 +234,14 @@ export default class ImageFile {
if (domImg.complete) {
return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
} else {
- return img.on('load', (function(_this) {
- return function() {
- return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
- };
- })(this));
+ return img.on(
+ 'load',
+ (function(_this) {
+ return function() {
+ return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
+ };
+ })(this),
+ );
}
}
}
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
index 3d89bf1316e..340a93e4e66 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
@@ -19,11 +19,13 @@ export default () => {
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
if (pipelineTableViewEl) {
- // Update MR and Commits tabs
- pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => {
- if (event.detail.pipelines &&
+ // Update MR and Commits tabs
+ pipelineTableViewEl.addEventListener('update-pipelines-count', event => {
+ if (
+ event.detail.pipelines &&
event.detail.pipelines.count &&
- event.detail.pipelines.count.all) {
+ event.detail.pipelines.count.all
+ ) {
const badge = document.querySelector('.js-pipelines-mr-count');
badge.textContent = event.detail.pipelines.count.all;
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index 4849b0fa3db..a2aa3d197e3 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -1,77 +1,73 @@
<script>
- import PipelinesService from '../../pipelines/services/pipelines_service';
- import PipelineStore from '../../pipelines/stores/pipelines_store';
- import pipelinesMixin from '../../pipelines/mixins/pipelines';
+import PipelinesService from '../../pipelines/services/pipelines_service';
+import PipelineStore from '../../pipelines/stores/pipelines_store';
+import pipelinesMixin from '../../pipelines/mixins/pipelines';
- export default {
- mixins: [
- pipelinesMixin,
- ],
- props: {
- endpoint: {
- type: String,
- required: true,
- },
- helpPagePath: {
- type: String,
- required: true,
- },
- autoDevopsHelpPath: {
- type: String,
- required: true,
- },
- errorStateSvgPath: {
- type: String,
- required: true,
- },
- viewType: {
- type: String,
- required: false,
- default: 'child',
- },
+export default {
+ mixins: [pipelinesMixin],
+ props: {
+ endpoint: {
+ type: String,
+ required: true,
},
+ helpPagePath: {
+ type: String,
+ required: true,
+ },
+ autoDevopsHelpPath: {
+ type: String,
+ required: true,
+ },
+ errorStateSvgPath: {
+ type: String,
+ required: true,
+ },
+ viewType: {
+ type: String,
+ required: false,
+ default: 'child',
+ },
+ },
- data() {
- const store = new PipelineStore();
+ data() {
+ const store = new PipelineStore();
- return {
- store,
- state: store.state,
- };
- },
+ return {
+ store,
+ state: store.state,
+ };
+ },
- computed: {
- shouldRenderTable() {
- return !this.isLoading &&
- this.state.pipelines.length > 0 &&
- !this.hasError;
- },
- shouldRenderErrorState() {
- return this.hasError && !this.isLoading;
- },
+ computed: {
+ shouldRenderTable() {
+ return !this.isLoading && this.state.pipelines.length > 0 && !this.hasError;
},
- created() {
- this.service = new PipelinesService(this.endpoint);
+ shouldRenderErrorState() {
+ return this.hasError && !this.isLoading;
},
- methods: {
- successCallback(resp) {
- // depending of the endpoint the response can either bring a `pipelines` key or not.
- const pipelines = resp.data.pipelines || resp.data;
- this.setCommonData(pipelines);
+ },
+ created() {
+ this.service = new PipelinesService(this.endpoint);
+ },
+ methods: {
+ successCallback(resp) {
+ // depending of the endpoint the response can either bring a `pipelines` key or not.
+ const pipelines = resp.data.pipelines || resp.data;
+ this.setCommonData(pipelines);
- const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
- detail: {
- pipelines: resp.data,
- },
- });
+ const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
+ detail: {
+ pipelines: resp.data,
+ },
+ });
- // notifiy to update the count in tabs
- if (this.$el.parentElement) {
- this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
- }
- },
+ // notifiy to update the count in tabs
+ if (this.$el.parentElement) {
+ this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
+ }
},
- };
+ },
+};
</script>
<template>
<div class="content-list pipelines">
diff --git a/app/assets/javascripts/commit_merge_requests.js b/app/assets/javascripts/commit_merge_requests.js
index 102b4ee8463..3a0ab119df6 100644
--- a/app/assets/javascripts/commit_merge_requests.js
+++ b/app/assets/javascripts/commit_merge_requests.js
@@ -50,7 +50,7 @@ export function createContent(mergeRequests) {
if (mergeRequests.length === 0) {
$content.text(s__('Commits|No related merge requests found'));
} else {
- mergeRequests.forEach((mergeRequest) => {
+ mergeRequests.forEach(mergeRequest => {
const $header = createHeader($content.children().length, mergeRequests.length);
const $item = createItem(mergeRequest);
$content.append($header);
@@ -64,8 +64,9 @@ export function createContent(mergeRequests) {
export function fetchCommitMergeRequests() {
const $container = $('.merge-requests');
- axios.get($container.data('projectCommitPath'))
- .then((response) => {
+ axios
+ .get($container.data('projectCommitPath'))
+ .then(response => {
const $content = createContent(response.data);
$container.html($content);
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 9a3ea7a55b6..54e2589c707 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -32,22 +32,31 @@ export default class CommitsList {
if (search === this.lastSearch) return Promise.resolve();
const commitsUrl = `${form.attr('action')}?${form.serialize()}`;
this.content.fadeTo('fast', 0.5);
- const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, {
- [obj.name]: obj.value,
- }), {});
+ const params = form.serializeArray().reduce(
+ (acc, obj) =>
+ Object.assign(acc, {
+ [obj.name]: obj.value,
+ }),
+ {},
+ );
- return axios.get(form.attr('action'), {
- params,
- })
+ return axios
+ .get(form.attr('action'), {
+ params,
+ })
.then(({ data }) => {
this.lastSearch = search;
this.content.html(data.html);
this.content.fadeTo('fast', 1.0);
// Change url so if user reload a page - search results are saved
- window.history.replaceState({
- page: commitsUrl,
- }, document.title, commitsUrl);
+ window.history.replaceState(
+ {
+ page: commitsUrl,
+ },
+ document.title,
+ commitsUrl,
+ );
})
.catch(() => {
this.content.fadeTo('fast', 1.0);
@@ -75,8 +84,15 @@ export default class CommitsList {
processedData = $processedData.not(`li.js-commit-header[data-day='${loadedShownDayFirst}']`);
// Update commits count in the previous commits header.
- commitsCount += Number($(processedData).nextUntil('li.js-commit-header').first().find('li.commit').length);
- $commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${pluralize('commit', commitsCount)}`);
+ commitsCount += Number(
+ $(processedData)
+ .nextUntil('li.js-commit-header')
+ .first()
+ .find('li.commit').length,
+ );
+ $commitsHeadersLast
+ .find('span.commits-count')
+ .text(`${commitsCount} ${pluralize('commit', commitsCount)}`);
}
localTimeAgo($processedData.find('.js-timeago'));
diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js
index 50e2949ab55..fba30aea9ae 100644
--- a/app/assets/javascripts/commons/bootstrap.js
+++ b/app/assets/javascripts/commons/bootstrap.js
@@ -5,6 +5,14 @@ import 'bootstrap';
// custom jQuery functions
$.fn.extend({
- disable() { return $(this).prop('disabled', true).addClass('disabled'); },
- enable() { return $(this).prop('disabled', false).removeClass('disabled'); },
+ disable() {
+ return $(this)
+ .prop('disabled', true)
+ .addClass('disabled');
+ },
+ enable() {
+ return $(this)
+ .prop('disabled', false)
+ .removeClass('disabled');
+ },
});
diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js
index 852d71f4e84..37a3ceb5341 100644
--- a/app/assets/javascripts/compare_autocomplete.js
+++ b/app/assets/javascripts/compare_autocomplete.js
@@ -40,7 +40,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = (
},
selectable: true,
filterable: true,
- filterRemote: true,
+ filterRemote: !!$dropdown.data('refsUrl'),
fieldName: $dropdown.data('fieldName'),
filterInput: 'input[type="search"]',
renderRow: function(ref) {
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
index b0c85c2572e..1000c310e35 100644
--- a/app/assets/javascripts/confirm_danger_modal.js
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -13,19 +13,23 @@ function openConfirmDangerModal($form, text) {
$submit.disable();
$input.focus();
- $('.js-confirm-danger-input').off('input').on('input', function handleInput() {
- const confirmText = rstrip($(this).val());
- if (confirmText === confirmTextMatch) {
- $submit.enable();
- } else {
- $submit.disable();
- }
- });
- $('.js-confirm-danger-submit').off('click').on('click', () => $form.submit());
+ $('.js-confirm-danger-input')
+ .off('input')
+ .on('input', function handleInput() {
+ const confirmText = rstrip($(this).val());
+ if (confirmText === confirmTextMatch) {
+ $submit.enable();
+ } else {
+ $submit.disable();
+ }
+ });
+ $('.js-confirm-danger-submit')
+ .off('click')
+ .on('click', () => $form.submit());
}
export default function initConfirmDangerModal() {
- $(document).on('click', '.js-confirm-danger', (e) => {
+ $(document).on('click', '.js-confirm-danger', e => {
e.preventDefault();
const $btn = $(e.target);
const $form = $btn.closest('form');
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index 3a50e73ad85..dff0adba25a 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -20,8 +20,11 @@ export default class ContextualSidebar {
}
bindEvents() {
- document.addEventListener('click', (e) => {
- if (!e.target.closest('.nav-sidebar') && (bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md')) {
+ document.addEventListener('click', e => {
+ if (
+ !e.target.closest('.nav-sidebar') &&
+ (bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md')
+ ) {
this.toggleCollapsedSidebar(true);
}
});
diff --git a/app/assets/javascripts/create_item_dropdown.js b/app/assets/javascripts/create_item_dropdown.js
index 8ef9aa7f529..916b190f469 100644
--- a/app/assets/javascripts/create_item_dropdown.js
+++ b/app/assets/javascripts/create_item_dropdown.js
@@ -36,7 +36,7 @@ export default class CreateItemDropdown {
},
selectable: true,
toggleLabel(selected) {
- return (selected && 'id' in selected) ? _.escape(selected.title) : this.defaultToggleLabel;
+ return selected && 'id' in selected ? _.escape(selected.title) : this.defaultToggleLabel;
},
fieldName: this.fieldName,
text(item) {
@@ -46,7 +46,7 @@ export default class CreateItemDropdown {
return _.escape(item.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
- clicked: (options) => {
+ clicked: options => {
options.e.preventDefault();
this.onSelect();
},
@@ -77,9 +77,8 @@ export default class CreateItemDropdown {
getData(term, callback) {
this.getDataOption(term, (data = []) => {
// Ensure the selected item isn't already in the data to avoid duplicates
- const alreadyHasSelectedItem = this.selectedItem && data.some(item =>
- item.id === this.selectedItem.id,
- );
+ const alreadyHasSelectedItem =
+ this.selectedItem && data.some(item => item.id === this.selectedItem.id);
let uniqueData = data;
if (!alreadyHasSelectedItem) {
@@ -106,9 +105,7 @@ export default class CreateItemDropdown {
if (newValue) {
this.selectedItem = this.createNewItemFromValue(newValue);
- this.$dropdownContainer
- .find('.js-dropdown-create-new-item code')
- .text(newValue);
+ this.$dropdownContainer.find('.js-dropdown-create-new-item code').text(newValue);
}
this.toggleFooter(!newValue);
diff --git a/app/assets/javascripts/create_label.js b/app/assets/javascripts/create_label.js
index a999c21b2e9..28ca7d97314 100644
--- a/app/assets/javascripts/create_label.js
+++ b/app/assets/javascripts/create_label.js
@@ -37,7 +37,7 @@ export default class CreateLabelDropdown {
addBinding() {
const self = this;
- this.$colorSuggestions.on('click', function (e) {
+ this.$colorSuggestions.on('click', function(e) {
const $this = $(this);
self.addColorValue(e, $this);
});
@@ -47,7 +47,7 @@ export default class CreateLabelDropdown {
this.$dropdownBack.on('click', this.resetForm.bind(this));
- this.$cancelButton.on('click', function (e) {
+ this.$cancelButton.on('click', function(e) {
e.preventDefault();
e.stopPropagation();
@@ -79,13 +79,9 @@ export default class CreateLabelDropdown {
}
resetForm() {
- this.$newLabelField
- .val('')
- .trigger('change');
+ this.$newLabelField.val('').trigger('change');
- this.$newColorField
- .val('')
- .trigger('change');
+ this.$newColorField.val('').trigger('change');
this.$colorPreview
.css('background-color', '')
@@ -97,31 +93,34 @@ export default class CreateLabelDropdown {
e.preventDefault();
e.stopPropagation();
- Api.newLabel(this.namespacePath, this.projectPath, {
- title: this.$newLabelField.val(),
- color: this.$newColorField.val(),
- }, (label) => {
- this.$newLabelCreateButton.enable();
-
- if (label.message) {
- let errors;
-
- if (typeof label.message === 'string') {
- errors = label.message;
+ Api.newLabel(
+ this.namespacePath,
+ this.projectPath,
+ {
+ title: this.$newLabelField.val(),
+ color: this.$newColorField.val(),
+ },
+ label => {
+ this.$newLabelCreateButton.enable();
+
+ if (label.message) {
+ let errors;
+
+ if (typeof label.message === 'string') {
+ errors = label.message;
+ } else {
+ errors = Object.keys(label.message)
+ .map(key => `${humanize(key)} ${label.message[key].join(', ')}`)
+ .join('<br/>');
+ }
+
+ this.$newLabelError.html(errors).show();
} else {
- errors = Object.keys(label.message).map(key =>
- `${humanize(key)} ${label.message[key].join(', ')}`,
- ).join('<br/>');
- }
+ this.$dropdownBack.trigger('click');
- this.$newLabelError
- .html(errors)
- .show();
- } else {
- this.$dropdownBack.trigger('click');
-
- $(document).trigger('created.label', label);
- }
- });
+ $(document).trigger('created.label', label);
+ }
+ },
+ );
}
}
diff --git a/app/assets/javascripts/cycle_analytics/components/banner.vue b/app/assets/javascripts/cycle_analytics/components/banner.vue
index 88570160f26..82b0f523d2e 100644
--- a/app/assets/javascripts/cycle_analytics/components/banner.vue
+++ b/app/assets/javascripts/cycle_analytics/components/banner.vue
@@ -1,28 +1,28 @@
<script>
- import Icon from '~/vue_shared/components/icon.vue';
- import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg';
+import Icon from '~/vue_shared/components/icon.vue';
+import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg';
- export default {
- components: {
- Icon,
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ documentationLink: {
+ type: String,
+ required: true,
},
- props: {
- documentationLink: {
- type: String,
- required: true,
- },
+ },
+ computed: {
+ iconCycleAnalyticsSplash() {
+ return iconCycleAnalyticsSplash;
},
- computed: {
- iconCycleAnalyticsSplash() {
- return iconCycleAnalyticsSplash;
- },
+ },
+ methods: {
+ dismissOverviewDialog() {
+ this.$emit('dismiss-overview-dialog');
},
- methods: {
- dismissOverviewDialog() {
- this.$emit('dismiss-overview-dialog');
- },
- },
- };
+ },
+};
</script>
<template>
<div class="landing content-block">
diff --git a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue
index b626b187651..f6a7d9962eb 100644
--- a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue
@@ -1,17 +1,17 @@
<script>
- import tooltip from '../../vue_shared/directives/tooltip';
+import tooltip from '../../vue_shared/directives/tooltip';
- export default {
- directives: {
- tooltip,
+export default {
+ directives: {
+ tooltip,
+ },
+ props: {
+ count: {
+ type: Number,
+ required: true,
},
- props: {
- count: {
- type: Number,
- required: true,
- },
- },
- };
+ },
+};
</script>
<template>
<span
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
index a71dcf78103..429fef176c3 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue
@@ -1,25 +1,25 @@
<script>
- import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
- import limitWarning from './limit_warning_component.vue';
- import totalTime from './total_time_component.vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+import limitWarning from './limit_warning_component.vue';
+import totalTime from './total_time_component.vue';
- export default {
- components: {
- userAvatarImage,
- limitWarning,
- totalTime,
+export default {
+ components: {
+ userAvatarImage,
+ limitWarning,
+ totalTime,
+ },
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
},
- props: {
- items: {
- type: Array,
- default: () => [],
- },
- stage: {
- type: Object,
- default: () => ({}),
- },
+ stage: {
+ type: Object,
+ default: () => ({}),
},
- };
+ },
+};
</script>
<template>
<div>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_component.vue
index 312fe75dde4..56e851fa528 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_component.vue
@@ -1,25 +1,25 @@
<script>
- import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
- import limitWarning from './limit_warning_component.vue';
- import totalTime from './total_time_component.vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+import limitWarning from './limit_warning_component.vue';
+import totalTime from './total_time_component.vue';
- export default {
- components: {
- userAvatarImage,
- limitWarning,
- totalTime,
+export default {
+ components: {
+ userAvatarImage,
+ limitWarning,
+ totalTime,
+ },
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
},
- props: {
- items: {
- type: Array,
- default: () => [],
- },
- stage: {
- type: Object,
- default: () => ({}),
- },
+ stage: {
+ type: Object,
+ default: () => ({}),
},
- };
+ },
+};
</script>
<template>
<div>
@@ -73,4 +73,3 @@
</ul>
</div>
</template>
-
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
index cee294b4ac2..54b9da4983a 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue
@@ -1,31 +1,31 @@
<script>
- import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
- import iconCommit from '../svg/icon_commit.svg';
- import limitWarning from './limit_warning_component.vue';
- import totalTime from './total_time_component.vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+import iconCommit from '../svg/icon_commit.svg';
+import limitWarning from './limit_warning_component.vue';
+import totalTime from './total_time_component.vue';
- export default {
- components: {
- userAvatarImage,
- totalTime,
- limitWarning,
+export default {
+ components: {
+ userAvatarImage,
+ totalTime,
+ limitWarning,
+ },
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
},
- props: {
- items: {
- type: Array,
- default: () => [],
- },
- stage: {
- type: Object,
- default: () => ({}),
- },
+ stage: {
+ type: Object,
+ default: () => ({}),
},
- computed: {
- iconCommit() {
- return iconCommit;
- },
+ },
+ computed: {
+ iconCommit() {
+ return iconCommit;
},
- };
+ },
+};
</script>
<template>
<div>
@@ -74,4 +74,3 @@
</ul>
</div>
</template>
-
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
index d4735d030fc..f9c80d237d7 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue
@@ -1,27 +1,27 @@
<script>
- import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
- import limitWarning from './limit_warning_component.vue';
- import totalTime from './total_time_component.vue';
- import icon from '../../vue_shared/components/icon.vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+import limitWarning from './limit_warning_component.vue';
+import totalTime from './total_time_component.vue';
+import icon from '../../vue_shared/components/icon.vue';
- export default {
- components: {
- userAvatarImage,
- totalTime,
- limitWarning,
- icon,
+export default {
+ components: {
+ userAvatarImage,
+ totalTime,
+ limitWarning,
+ icon,
+ },
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
},
- props: {
- items: {
- type: Array,
- default: () => [],
- },
- stage: {
- type: Object,
- default: () => ({}),
- },
+ stage: {
+ type: Object,
+ default: () => ({}),
},
- };
+ },
+};
</script>
<template>
<div>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
index 22637485c01..e83b66eef86 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue
@@ -1,33 +1,33 @@
<script>
- import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
- import iconBranch from '../svg/icon_branch.svg';
- import limitWarning from './limit_warning_component.vue';
- import totalTime from './total_time_component.vue';
- import icon from '../../vue_shared/components/icon.vue';
+import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
+import iconBranch from '../svg/icon_branch.svg';
+import limitWarning from './limit_warning_component.vue';
+import totalTime from './total_time_component.vue';
+import icon from '../../vue_shared/components/icon.vue';
- export default {
- components: {
- userAvatarImage,
- totalTime,
- limitWarning,
- icon,
+export default {
+ components: {
+ userAvatarImage,
+ totalTime,
+ limitWarning,
+ icon,
+ },
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
},
- props: {
- items: {
- type: Array,
- default: () => [],
- },
- stage: {
- type: Object,
- default: () => ({}),
- },
+ stage: {
+ type: Object,
+ default: () => ({}),
},
- computed: {
- iconBranch() {
- return iconBranch;
- },
+ },
+ computed: {
+ iconBranch() {
+ return iconBranch;
},
- };
+ },
+};
</script>
<template>
<div>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
index a0796f299e7..a8196dc879a 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue
@@ -1,35 +1,35 @@
<script>
- import iconBuildStatus from '../svg/icon_build_status.svg';
- import iconBranch from '../svg/icon_branch.svg';
- import limitWarning from './limit_warning_component.vue';
- import totalTime from './total_time_component.vue';
- import icon from '../../vue_shared/components/icon.vue';
+import iconBuildStatus from '../svg/icon_build_status.svg';
+import iconBranch from '../svg/icon_branch.svg';
+import limitWarning from './limit_warning_component.vue';
+import totalTime from './total_time_component.vue';
+import icon from '../../vue_shared/components/icon.vue';
- export default {
- components: {
- totalTime,
- limitWarning,
- icon,
+export default {
+ components: {
+ totalTime,
+ limitWarning,
+ icon,
+ },
+ props: {
+ items: {
+ type: Array,
+ default: () => [],
},
- props: {
- items: {
- type: Array,
- default: () => [],
- },
- stage: {
- type: Object,
- default: () => ({}),
- },
+ stage: {
+ type: Object,
+ default: () => ({}),
},
- computed: {
- iconBuildStatus() {
- return iconBuildStatus;
- },
- iconBranch() {
- return iconBranch;
- },
+ },
+ computed: {
+ iconBuildStatus() {
+ return iconBuildStatus;
},
- };
+ iconBranch() {
+ return iconBranch;
+ },
+ },
+};
</script>
<template>
<div>
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.vue b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
index 7758bf0cb3f..4db50134208 100644
--- a/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
+++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue
@@ -1,18 +1,18 @@
<script>
- export default {
- props: {
- time: {
- type: Object,
- required: false,
- default: () => ({}),
- },
+export default {
+ props: {
+ time: {
+ type: Object,
+ required: false,
+ default: () => ({}),
},
- computed: {
- hasData() {
- return Object.keys(this.time).length;
- },
+ },
+ computed: {
+ hasData() {
+ return Object.keys(this.time).length;
},
- };
+ },
+};
</script>
<template>
<span class="total-time">
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 1c43fc3cdc7..4de425b48e7 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -18,7 +18,8 @@ Vue.use(Translate);
export default () => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
- new Vue({ // eslint-disable-line no-new
+ // eslint-disable-next-line no-new
+ new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
components: {
@@ -66,14 +67,17 @@ export default () => {
const $dropdown = $('.js-ca-dropdown');
const $label = $dropdown.find('.dropdown-label');
- $dropdown.find('li a').off('click').on('click', (e) => {
- e.preventDefault();
- const $target = $(e.currentTarget);
- this.startDate = $target.data('value');
+ $dropdown
+ .find('li a')
+ .off('click')
+ .on('click', e => {
+ e.preventDefault();
+ const $target = $(e.currentTarget);
+ this.startDate = $target.data('value');
- $label.text($target.text().trim());
- this.fetchCycleAnalyticsData({ startDate: this.startDate });
- });
+ $label.text($target.text().trim());
+ this.fetchCycleAnalyticsData({ startDate: this.startDate });
+ });
},
fetchCycleAnalyticsData(options) {
const fetchOptions = options || { startDate: this.startDate };
@@ -82,7 +86,7 @@ export default () => {
this.service
.fetchCycleAnalyticsData(fetchOptions)
- .then((response) => {
+ .then(response => {
this.store.setCycleAnalyticsData(response);
this.selectDefaultStage();
this.initDropdown();
@@ -115,7 +119,7 @@ export default () => {
stage,
startDate: this.startDate,
})
- .then((response) => {
+ .then(response => {
this.isEmptyStage = !response.events.length;
this.store.setStageEvents(response.events, stage);
this.isLoadingStage = false;
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
index 4cf416c50e5..a0426301a0a 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
@@ -18,10 +18,7 @@ export default class CycleAnalyticsService {
}
fetchStageData(options) {
- const {
- stage,
- startDate,
- } = options;
+ const { stage, startDate } = options;
return this.axios
.get(`events/${stage.name}.json`, {
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
index a8cd8c20f8f..18fb57c8b4f 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
@@ -5,13 +5,27 @@ import { dasherize } from '../lib/utils/text_utility';
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
const EMPTY_STAGE_TEXTS = {
- issue: __('The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.'),
- plan: __('The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.'),
- code: __('The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.'),
- test: __('The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.'),
- review: __('The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.'),
- staging: __('The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.'),
- production: __('The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.'),
+ issue: __(
+ 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
+ ),
+ plan: __(
+ 'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
+ ),
+ code: __(
+ 'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
+ ),
+ test: __(
+ 'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
+ ),
+ review: __(
+ 'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
+ ),
+ staging: __(
+ 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
+ ),
+ production: __(
+ 'The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.',
+ ),
};
export default {
@@ -31,11 +45,11 @@ export default {
newData.stages = data.stats || [];
newData.summary = data.summary || [];
- newData.summary.forEach((item) => {
+ newData.summary.forEach(item => {
item.value = item.value || '-';
});
- newData.stages.forEach((item) => {
+ newData.stages.forEach(item => {
const stageSlug = dasherize(item.name.toLowerCase());
item.active = false;
item.isUserAllowed = data.permissions[stageSlug];
@@ -53,7 +67,7 @@ export default {
this.state.hasError = state;
},
deactivateAllStages() {
- this.state.stages.forEach((stage) => {
+ this.state.stages.forEach(stage => {
stage.active = false;
});
},
@@ -67,7 +81,7 @@ export default {
decorateEvents(events, stage) {
const newEvents = [];
- events.forEach((item) => {
+ events.forEach(item => {
if (!item) return;
const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index aa52f120fe7..3589599986d 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -95,8 +95,10 @@ export default {
.catch(() => new Flash(s__('DeployKeys|Error enabling deploy key')));
},
disableKey(deployKey, callback) {
- // eslint-disable-next-line no-alert
- if (window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))) {
+ if (
+ // eslint-disable-next-line no-alert
+ window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))
+ ) {
this.service
.disableKey(deployKey.id)
.then(this.fetchKeys)
diff --git a/app/assets/javascripts/deploy_keys/service/index.js b/app/assets/javascripts/deploy_keys/service/index.js
index 9dc3b21f6f6..268a37008c5 100644
--- a/app/assets/javascripts/deploy_keys/service/index.js
+++ b/app/assets/javascripts/deploy_keys/service/index.js
@@ -8,17 +8,14 @@ export default class DeployKeysService {
}
getKeys() {
- return this.axios.get()
- .then(response => response.data);
+ return this.axios.get().then(response => response.data);
}
enableKey(id) {
- return this.axios.put(`${id}/enable`)
- .then(response => response.data);
+ return this.axios.put(`${id}/enable`).then(response => response.data);
}
disableKey(id) {
- return this.axios.put(`${id}/disable`)
- .then(response => response.data);
+ return this.axios.put(`${id}/disable`).then(response => response.data);
}
}
diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js
index a044fc1ab42..245f1a7c558 100644
--- a/app/assets/javascripts/diff.js
+++ b/app/assets/javascripts/diff.js
@@ -21,9 +21,12 @@ export default class Diff {
});
const tab = document.getElementById('diffs');
- if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== '')) FilesCommentButton.init($diffFile);
+ if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== ''))
+ FilesCommentButton.init($diffFile);
- const firstFile = $('.files').first().get(0);
+ const firstFile = $('.files')
+ .first()
+ .get(0);
const canCreateNote = firstFile && firstFile.hasAttribute('data-can-create-note');
$diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote));
@@ -73,9 +76,10 @@ export default class Diff {
const view = file.data('view');
const params = { since, to, bottom, offset, unfold, view };
- axios.get(link, { params })
- .then(({ data }) => $target.parent().replaceWith(data))
- .catch(() => flash(__('An error occurred while loading diff')));
+ axios
+ .get(link, { params })
+ .then(({ data }) => $target.parent().replaceWith(data))
+ .catch(() => flash(__('An error occurred while loading diff')));
}
openAnchoredDiff(cb) {
diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
index 87621761500..4ae4ceabc21 100644
--- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
+++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
@@ -18,52 +18,56 @@ const CommentAndResolveBtn = Vue.extend({
};
},
computed: {
- showButton: function () {
+ showButton: function() {
if (this.discussion) {
return this.discussion.isResolvable();
} else {
return false;
}
},
- isDiscussionResolved: function () {
+ isDiscussionResolved: function() {
return this.discussion.isResolved();
},
- buttonText: function () {
+ buttonText: function() {
if (this.isDiscussionResolved) {
if (this.textareaIsEmpty) {
- return "Unresolve discussion";
+ return 'Unresolve discussion';
} else {
- return "Comment & unresolve discussion";
+ return 'Comment & unresolve discussion';
}
} else {
if (this.textareaIsEmpty) {
- return "Resolve discussion";
+ return 'Resolve discussion';
} else {
- return "Comment & resolve discussion";
+ return 'Comment & resolve discussion';
}
}
- }
+ },
},
created() {
if (this.discussionId) {
this.discussion = CommentsStore.state[this.discussionId];
}
},
- mounted: function () {
+ mounted: function() {
if (!this.discussionId) return;
- const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`);
+ const $textarea = $(
+ `.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`,
+ );
this.textareaIsEmpty = $textarea.val() === '';
$textarea.on('input.comment-and-resolve-btn', () => {
this.textareaIsEmpty = $textarea.val() === '';
});
},
- destroyed: function () {
+ destroyed: function() {
if (!this.discussionId) return;
- $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn');
- }
+ $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off(
+ 'input.comment-and-resolve-btn',
+ );
+ },
});
Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
index 5528d2a542b..5bdeaaade68 100644
--- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
+++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js
@@ -83,7 +83,11 @@ const DiffNoteAvatars = Vue.extend({
this.addNoCommentClass();
this.setDiscussionVisible();
- this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
+ this.lineType = $(this.$el)
+ .closest('.diff-line-num')
+ .hasClass('old_line')
+ ? 'old'
+ : 'new';
});
$(document).on('toggle.comments', () => {
@@ -113,20 +117,30 @@ const DiffNoteAvatars = Vue.extend({
addNoCommentClass() {
const { notesCount } = this;
- $(this.$el).closest('.js-avatar-container')
+ $(this.$el)
+ .closest('.js-avatar-container')
.toggleClass('no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container')
.toggleClass('no-comment-btn', notesCount > 0);
},
toggleDiscussionsToggleState() {
- const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
+ const $notesHolders = $(this.$el)
+ .closest('.code')
+ .find('.notes_holder');
const $visibleNotesHolders = $notesHolders.filter(':visible');
- const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments');
-
- $toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length);
+ const $toggleDiffCommentsBtn = $(this.$el)
+ .closest('.diff-file')
+ .find('.js-toggle-diff-comments');
+
+ $toggleDiffCommentsBtn.toggleClass(
+ 'active',
+ $notesHolders.length === $visibleNotesHolders.length,
+ );
},
setDiscussionVisible() {
- this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
+ this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(
+ ':visible',
+ );
},
getTooltipText(note) {
return `${note.authorName}: ${note.noteTruncated}`;
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
index 2b78bb58735..c0c21416275 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
@@ -14,24 +14,24 @@ const JumpToDiscussion = Vue.extend({
required: true,
},
},
- data: function () {
+ data: function() {
return {
discussions: CommentsStore.state,
discussion: {},
};
},
computed: {
- buttonText: function () {
+ buttonText: function() {
if (this.discussionId) {
return 'Jump to next unresolved discussion';
} else {
return 'Jump to first unresolved discussion';
}
},
- allResolved: function () {
+ allResolved: function() {
return this.unresolvedDiscussionCount === 0;
},
- showButton: function () {
+ showButton: function() {
if (this.discussionId) {
if (this.unresolvedDiscussionCount > 1) {
return true;
@@ -42,7 +42,7 @@ const JumpToDiscussion = Vue.extend({
return this.unresolvedDiscussionCount >= 1;
}
},
- lastResolvedId: function () {
+ lastResolvedId: function() {
let lastId;
for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId];
@@ -52,13 +52,13 @@ const JumpToDiscussion = Vue.extend({
}
}
return lastId;
- }
+ },
},
created() {
this.discussion = this.discussions[this.discussionId];
},
methods: {
- jumpToNextUnresolvedDiscussion: function () {
+ jumpToNextUnresolvedDiscussion: function() {
let discussionsSelector;
let discussionIdsInScope;
let firstUnresolvedDiscussionId;
@@ -68,9 +68,11 @@ const JumpToDiscussion = Vue.extend({
let jumpToFirstDiscussion = !this.discussionId;
const discussionIdsForElements = function(elements) {
- return elements.map(function() {
- return $(this).attr('data-discussion-id');
- }).toArray();
+ return elements
+ .map(function() {
+ return $(this).attr('data-discussion-id');
+ })
+ .toArray();
};
const { discussions } = this;
@@ -144,8 +146,7 @@ const JumpToDiscussion = Vue.extend({
if (!discussion.isResolved()) {
nextUnresolvedDiscussionId = discussionId;
break;
- }
- else {
+ } else {
continue;
}
}
@@ -175,9 +176,9 @@ const JumpToDiscussion = Vue.extend({
// Resolved discussions are hidden in the diffs tab by default.
// If they are marked unresolved on the notes tab, they will still be hidden on the diffs tab.
// When jumping between unresolved discussions on the diffs tab, we show them.
- $target.closest(".content").show();
+ $target.closest('.content').show();
- const $notesHolder = $target.closest("tr.notes_holder");
+ const $notesHolder = $target.closest('tr.notes_holder');
// Image diff discussions does not use notes_holder
// so we should keep original $target value in those cases
@@ -194,7 +195,7 @@ const JumpToDiscussion = Vue.extend({
prevEl = $target.prev();
// If the discussion doesn't have 4 lines above it, we'll have to do with fewer.
- if (!prevEl.hasClass("line_holder")) {
+ if (!prevEl.hasClass('line_holder')) {
break;
}
@@ -203,9 +204,9 @@ const JumpToDiscussion = Vue.extend({
}
$.scrollTo($target, {
- offset: -150
+ offset: -150,
});
- }
+ },
},
});
diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js b/app/assets/javascripts/diff_notes/components/resolve_count.js
index eb539c6b348..d8b056096f4 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_count.js
+++ b/app/assets/javascripts/diff_notes/components/resolve_count.js
@@ -13,17 +13,17 @@ window.ResolveCount = Vue.extend({
required: true,
},
},
- data: function () {
+ data: function() {
return {
- discussions: CommentsStore.state
+ discussions: CommentsStore.state,
};
},
computed: {
- allResolved: function () {
+ allResolved: function() {
return this.resolvedDiscussionCount === this.discussionCount;
},
resolvedCountText() {
return this.discussionCount === 1 ? 'discussion' : 'discussions';
- }
- }
+ },
+ },
});
diff --git a/app/assets/javascripts/diff_notes/mixins/discussion.js b/app/assets/javascripts/diff_notes/mixins/discussion.js
index 7589f9dd6e0..dea64dca132 100644
--- a/app/assets/javascripts/diff_notes/mixins/discussion.js
+++ b/app/assets/javascripts/diff_notes/mixins/discussion.js
@@ -2,10 +2,10 @@
const DiscussionMixins = {
computed: {
- discussionCount: function () {
+ discussionCount: function() {
return Object.keys(this.discussions).length;
},
- resolvedDiscussionCount: function () {
+ resolvedDiscussionCount: function() {
let resolvedCount = 0;
for (const discussionId in this.discussions) {
@@ -18,7 +18,7 @@ const DiscussionMixins = {
return resolvedCount;
},
- unresolvedDiscussionCount: function () {
+ unresolvedDiscussionCount: function() {
let unresolvedCount = 0;
for (const discussionId in this.discussions) {
@@ -30,8 +30,8 @@ const DiscussionMixins = {
}
return unresolvedCount;
- }
- }
+ },
+ },
};
export default DiscussionMixins;
diff --git a/app/assets/javascripts/diff_notes/models/discussion.js b/app/assets/javascripts/diff_notes/models/discussion.js
index 787e6d8855f..daf61e5d467 100644
--- a/app/assets/javascripts/diff_notes/models/discussion.js
+++ b/app/assets/javascripts/diff_notes/models/discussion.js
@@ -6,22 +6,22 @@ import Vue from 'vue';
import { localTimeAgo } from '../../lib/utils/datetime_utility';
class DiscussionModel {
- constructor (discussionId) {
+ constructor(discussionId) {
this.id = discussionId;
this.notes = {};
this.loading = false;
this.canResolve = false;
}
- createNote (noteObj) {
+ createNote(noteObj) {
Vue.set(this.notes, noteObj.noteId, new NoteModel(this.id, noteObj));
}
- deleteNote (noteId) {
+ deleteNote(noteId) {
Vue.delete(this.notes, noteId);
}
- getNote (noteId) {
+ getNote(noteId) {
return this.notes[noteId];
}
@@ -29,7 +29,7 @@ class DiscussionModel {
return Object.keys(this.notes).length;
}
- isResolved () {
+ isResolved() {
for (const noteId in this.notes) {
const note = this.notes[noteId];
@@ -40,7 +40,7 @@ class DiscussionModel {
return true;
}
- resolveAllNotes (resolved_by) {
+ resolveAllNotes(resolved_by) {
for (const noteId in this.notes) {
const note = this.notes[noteId];
@@ -51,7 +51,7 @@ class DiscussionModel {
}
}
- unResolveAllNotes () {
+ unResolveAllNotes() {
for (const noteId in this.notes) {
const note = this.notes[noteId];
@@ -62,7 +62,7 @@ class DiscussionModel {
}
}
- updateHeadline (data) {
+ updateHeadline(data) {
const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`;
const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`);
@@ -79,7 +79,7 @@ class DiscussionModel {
}
}
- isResolvable () {
+ isResolvable() {
if (!this.canResolve) {
return false;
}
diff --git a/app/assets/javascripts/diff_notes/stores/comments.js b/app/assets/javascripts/diff_notes/stores/comments.js
index d012cd02d10..060bb044f78 100644
--- a/app/assets/javascripts/diff_notes/stores/comments.js
+++ b/app/assets/javascripts/diff_notes/stores/comments.js
@@ -5,10 +5,10 @@ import Vue from 'vue';
window.CommentsStore = {
state: {},
- get: function (discussionId, noteId) {
+ get: function(discussionId, noteId) {
return this.state[discussionId].getNote(noteId);
},
- createDiscussion: function (discussionId, canResolve) {
+ createDiscussion: function(discussionId, canResolve) {
let discussion = this.state[discussionId];
if (!this.state[discussionId]) {
discussion = new DiscussionModel(discussionId);
@@ -21,18 +21,18 @@ window.CommentsStore = {
return discussion;
},
- create: function (noteObj) {
+ create: function(noteObj) {
const discussion = this.createDiscussion(noteObj.discussionId);
discussion.createNote(noteObj);
},
- update: function (discussionId, noteId, resolved, resolved_by) {
+ update: function(discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId];
const note = discussion.getNote(noteId);
note.resolved = resolved;
note.resolved_by = resolved_by;
},
- delete: function (discussionId, noteId) {
+ delete: function(discussionId, noteId) {
const discussion = this.state[discussionId];
discussion.deleteNote(noteId);
@@ -40,7 +40,7 @@ window.CommentsStore = {
Vue.delete(this.state, discussionId);
}
},
- unresolvedDiscussionIds: function () {
+ unresolvedDiscussionIds: function() {
const ids = [];
for (const discussionId in this.state) {
@@ -52,5 +52,5 @@ window.CommentsStore = {
}
return ids;
- }
+ },
};
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index edca45f22f9..a8d615dd8f0 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -41,6 +41,11 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ assignedDiscussions: false,
+ };
+ },
computed: {
...mapState({
isLoading: state => state.diffs.isLoading,
@@ -58,9 +63,9 @@ export default {
plainDiffPath: state => state.diffs.plainDiffPath,
emailPatchPath: state => state.diffs.emailPatchPath,
}),
- ...mapState('diffs', ['showTreeList']),
+ ...mapState('diffs', ['showTreeList', 'isLoading']),
...mapGetters('diffs', ['isParallelView']),
- ...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
+ ...mapGetters(['isNotesFetched', 'getNoteableData']),
targetBranch() {
return {
branchName: this.targetBranchName,
@@ -147,11 +152,12 @@ export default {
}
},
setDiscussions() {
- if (this.isNotesFetched) {
+ if (this.isNotesFetched && !this.assignedDiscussions && !this.isLoading) {
requestIdleCallback(
- () => {
- this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
- },
+ () =>
+ this.assignDiscussionsToDiff().then(() => {
+ this.assignedDiscussions = true;
+ }),
{ timeout: 1000 },
);
}
diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index 993206b2e73..23d0bad2ecb 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -43,7 +43,9 @@ export default {
return (this.commit.author && this.commit.author.name) || this.commit.authorName;
},
authorUrl() {
- return (this.commit.author && this.commit.author.webUrl) || `mailto:${this.commit.authorEmail}`;
+ return (
+ (this.commit.author && this.commit.author.webUrl) || `mailto:${this.commit.authorEmail}`
+ );
},
authorAvatar() {
return (this.commit.author && this.commit.author.avatarUrl) || this.commit.authorGravatarUrl;
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 4e04e50c52a..958e57c5652 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -29,7 +29,7 @@ export default {
},
computed: {
...mapState('diffs', ['currentDiffFileId']),
- ...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
+ ...mapGetters(['isNotesFetched']),
isCollapsed() {
return this.file.collapsed || false;
},
@@ -46,10 +46,10 @@ export default {
showExpandMessage() {
return (
this.isCollapsed ||
- !this.file.highlightedDiffLines &&
- !this.isLoadingCollapsedDiff &&
- !this.file.tooLarge &&
- this.file.text
+ (!this.file.highlightedDiffLines &&
+ !this.isLoadingCollapsedDiff &&
+ !this.file.tooLarge &&
+ this.file.text)
);
},
showLoadingIcon() {
@@ -79,7 +79,7 @@ export default {
.then(() => {
requestIdleCallback(
() => {
- this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
+ this.assignDiscussionsToDiff();
},
{ timeout: 1000 },
);
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index cfe4273742f..34e836a570a 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -1,17 +1,30 @@
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
+import { TooltipDirective as Tooltip } from '@gitlab-org/gitlab-ui';
+import { convertPermissionToBoolean } from '~/lib/utils/common_utils';
import Icon from '~/vue_shared/components/icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileRowStats from './file_row_stats.vue';
+const treeListStorageKey = 'mr_diff_tree_list';
+
export default {
+ directives: {
+ Tooltip,
+ },
components: {
Icon,
FileRow,
},
data() {
+ const treeListStored = localStorage.getItem(treeListStorageKey);
+ const renderTreeList = treeListStored !== null ?
+ convertPermissionToBoolean(treeListStored) : true;
+
return {
search: '',
+ renderTreeList,
+ focusSearch: false,
};
},
computed: {
@@ -20,15 +33,35 @@ export default {
filteredTreeList() {
const search = this.search.toLowerCase().trim();
- if (search === '') return this.tree;
+ if (search === '') return this.renderTreeList ? this.tree : this.allBlobs;
return this.allBlobs.filter(f => f.name.toLowerCase().indexOf(search) >= 0);
},
+ rowDisplayTextKey() {
+ if (this.renderTreeList && this.search.trim() === '') {
+ return 'name';
+ }
+
+ return 'path';
+ },
},
methods: {
...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']),
clearSearch() {
this.search = '';
+ this.toggleFocusSearch(false);
+ },
+ toggleRenderTreeList(toggle) {
+ this.renderTreeList = toggle;
+ localStorage.setItem(treeListStorageKey, this.renderTreeList);
+ },
+ toggleFocusSearch(toggle) {
+ this.focusSearch = toggle;
+ },
+ blurSearch() {
+ if (this.search.trim() === '') {
+ this.toggleFocusSearch(false);
+ }
},
},
FileRowStats,
@@ -37,28 +70,67 @@ export default {
<template>
<div class="tree-list-holder d-flex flex-column">
- <div class="append-bottom-8 position-relative tree-list-search">
- <icon
- name="search"
- class="position-absolute tree-list-icon"
- />
- <input
- v-model="search"
- :placeholder="s__('MergeRequest|Filter files')"
- type="search"
- class="form-control"
- />
- <button
- v-show="search"
- :aria-label="__('Clear search')"
- type="button"
- class="position-absolute tree-list-icon tree-list-clear-icon border-0 p-0"
- @click="clearSearch"
- >
+ <div class="append-bottom-8 position-relative tree-list-search d-flex">
+ <div class="flex-fill d-flex">
<icon
- name="close"
+ name="search"
+ class="position-absolute tree-list-icon"
+ />
+ <input
+ v-model="search"
+ :placeholder="s__('MergeRequest|Filter files')"
+ type="search"
+ class="form-control"
+ @focus="toggleFocusSearch(true)"
+ @blur="blurSearch"
/>
- </button>
+ <button
+ v-show="search"
+ :aria-label="__('Clear search')"
+ type="button"
+ class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0"
+ @click="clearSearch"
+ >
+ <icon
+ name="close"
+ />
+ </button>
+ </div>
+ <div
+ v-show="!focusSearch"
+ class="btn-group prepend-left-8 tree-list-view-toggle"
+ >
+ <button
+ v-tooltip.hover
+ :aria-label="__('List view')"
+ :title="__('List view')"
+ :class="{
+ active: !renderTreeList
+ }"
+ class="btn btn-default pt-0 pb-0 d-flex align-items-center"
+ type="button"
+ @click="toggleRenderTreeList(false)"
+ >
+ <icon
+ name="hamburger"
+ />
+ </button>
+ <button
+ v-tooltip.hover
+ :aria-label="__('Tree view')"
+ :title="__('Tree view')"
+ :class="{
+ active: renderTreeList
+ }"
+ class="btn btn-default pt-0 pb-0 d-flex align-items-center"
+ type="button"
+ @click="toggleRenderTreeList(true)"
+ >
+ <icon
+ name="file-tree"
+ />
+ </button>
+ </div>
</div>
<div
class="tree-list-scroll"
@@ -72,6 +144,8 @@ export default {
:hide-extra-on-tree="true"
:extra-component="$options.FileRowStats"
:show-changed-icon="true"
+ :display-text-key="rowDisplayTextKey"
+ :should-truncate-start="true"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="scrollToFile"
/>
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 1e0b27b538d..ca8ae605cb4 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -5,7 +5,6 @@ import createFlash from '~/flash';
import { s__ } from '~/locale';
import { handleLocationHash, historyPushState } from '~/lib/utils/common_utils';
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
-import { reduceDiscussionsToLineCodes } from '../../notes/stores/utils';
import { getDiffPositionByLineCode, getNoteFormData } from './utils';
import * as types from './mutation_types';
import {
@@ -36,18 +35,17 @@ export const fetchDiffFiles = ({ state, commit }) => {
// This is adding line discussions to the actual lines in the diff tree
// once for parallel and once for inline mode
-export const assignDiscussionsToDiff = ({ state, commit }, allLineDiscussions) => {
+export const assignDiscussionsToDiff = (
+ { commit, state, rootState },
+ discussions = rootState.notes.discussions,
+) => {
const diffPositionByLineCode = getDiffPositionByLineCode(state.diffFiles);
- Object.values(allLineDiscussions).forEach(discussions => {
- if (discussions.length > 0) {
- const { fileHash } = discussions[0];
- commit(types.SET_LINE_DISCUSSIONS_FOR_FILE, {
- fileHash,
- discussions,
- diffPositionByLineCode,
- });
- }
+ discussions.filter(discussion => discussion.diff_discussion).forEach(discussion => {
+ commit(types.SET_LINE_DISCUSSIONS_FOR_FILE, {
+ discussion,
+ diffPositionByLineCode,
+ });
});
};
@@ -190,9 +188,7 @@ export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => {
return dispatch('saveNote', postData, { root: true })
.then(result => dispatch('updateDiscussion', result.discussion, { root: true }))
- .then(discussion =>
- dispatch('assignDiscussionsToDiff', reduceDiscussionsToLineCodes([discussion])),
- )
+ .then(discussion => dispatch('assignDiscussionsToDiff', [discussion]))
.catch(() => createFlash(s__('MergeRequests|Saving the comment failed')));
};
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 0b4485ecdb5..5a8aebd2086 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -90,53 +90,67 @@ export default {
}));
},
- [types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, discussions, diffPositionByLineCode }) {
- const selectedFile = state.diffFiles.find(f => f.fileHash === fileHash);
- const firstDiscussion = discussions[0];
- const isDiffDiscussion = firstDiscussion.diff_discussion;
- const hasLineCode = firstDiscussion.line_code;
- const diffPosition = diffPositionByLineCode[firstDiscussion.line_code];
-
- if (
- selectedFile &&
- isDiffDiscussion &&
- hasLineCode &&
- diffPosition &&
+ [types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { discussion, diffPositionByLineCode }) {
+ const { latestDiff } = state;
+
+ const discussionLineCode = discussion.line_code;
+ const fileHash = discussion.diff_file.file_hash;
+ const lineCheck = ({ lineCode }) =>
+ lineCode === discussionLineCode &&
isDiscussionApplicableToLine({
- discussion: firstDiscussion,
- diffPosition,
- latestDiff: state.latestDiff,
- })
- ) {
- const targetLine = selectedFile.parallelDiffLines.find(
- line =>
- (line.left && line.left.lineCode === firstDiscussion.line_code) ||
- (line.right && line.right.lineCode === firstDiscussion.line_code),
- );
- if (targetLine) {
- if (targetLine.left && targetLine.left.lineCode === firstDiscussion.line_code) {
- Object.assign(targetLine.left, {
- discussions,
- });
- } else {
- Object.assign(targetLine.right, {
- discussions,
+ discussion,
+ diffPosition: diffPositionByLineCode[lineCode],
+ latestDiff,
+ });
+
+ state.diffFiles = state.diffFiles.map(diffFile => {
+ if (diffFile.fileHash === fileHash) {
+ const file = { ...diffFile };
+
+ if (file.highlightedDiffLines) {
+ file.highlightedDiffLines = file.highlightedDiffLines.map(line => {
+ if (lineCheck(line)) {
+ return {
+ ...line,
+ discussions: line.discussions.concat(discussion),
+ };
+ }
+
+ return line;
});
}
- }
-
- if (selectedFile.highlightedDiffLines) {
- const targetInlineLine = selectedFile.highlightedDiffLines.find(
- line => line.lineCode === firstDiscussion.line_code,
- );
- if (targetInlineLine) {
- Object.assign(targetInlineLine, {
- discussions,
+ if (file.parallelDiffLines) {
+ file.parallelDiffLines = file.parallelDiffLines.map(line => {
+ const left = line.left && lineCheck(line.left);
+ const right = line.right && lineCheck(line.right);
+
+ if (left || right) {
+ return {
+ left: {
+ ...line.left,
+ discussions: left ? line.left.discussions.concat(discussion) : [],
+ },
+ right: {
+ ...line.right,
+ discussions: right ? line.right.discussions.concat(discussion) : [],
+ },
+ };
+ }
+
+ return line;
});
}
+
+ if (!file.parallelDiffLines || !file.highlightedDiffLines) {
+ file.discussions = file.discussions.concat(discussion);
+ }
+
+ return file;
}
- }
+
+ return diffFile;
+ });
},
[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) {
diff --git a/app/assets/javascripts/droplab/constants.js b/app/assets/javascripts/droplab/constants.js
index 868d47e91b3..6451af49d36 100644
--- a/app/assets/javascripts/droplab/constants.js
+++ b/app/assets/javascripts/droplab/constants.js
@@ -6,11 +6,4 @@ const IGNORE_CLASS = 'droplab-item-ignore';
// Matches `{{anything}}` and `{{ everything }}`.
const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g;
-export {
- DATA_TRIGGER,
- DATA_DROPDOWN,
- SELECTED_CLASS,
- ACTIVE_CLASS,
- TEMPLATE_REGEX,
- IGNORE_CLASS,
-};
+export { DATA_TRIGGER, DATA_DROPDOWN, SELECTED_CLASS, ACTIVE_CLASS, TEMPLATE_REGEX, IGNORE_CLASS };
diff --git a/app/assets/javascripts/droplab/drop_down.js b/app/assets/javascripts/droplab/drop_down.js
index 3cc316c3f3e..ccb3d56ed8c 100644
--- a/app/assets/javascripts/droplab/drop_down.js
+++ b/app/assets/javascripts/droplab/drop_down.js
@@ -2,7 +2,7 @@ import utils from './utils';
import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
class DropDown {
- constructor(list, config = { }) {
+ constructor(list, config = {}) {
this.currentIndex = 0;
this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list;
@@ -157,7 +157,7 @@ class DropDown {
static setImagesSrc(template) {
const images = [...template.querySelectorAll('img[data-src]')];
- images.forEach((image) => {
+ images.forEach(image => {
const img = image;
img.src = img.getAttribute('data-src');
diff --git a/app/assets/javascripts/droplab/drop_lab.js b/app/assets/javascripts/droplab/drop_lab.js
index 2a02ede72bf..1339e28d8b8 100644
--- a/app/assets/javascripts/droplab/drop_lab.js
+++ b/app/assets/javascripts/droplab/drop_lab.js
@@ -51,7 +51,7 @@ class DropLab {
}
processData(trigger, data, methodName) {
- this.hooks.forEach((hook) => {
+ this.hooks.forEach(hook => {
if (Array.isArray(trigger)) hook.list[methodName](trigger);
if (hook.trigger.id === trigger) hook.list[methodName](data);
@@ -78,7 +78,8 @@ class DropLab {
}
changeHookList(trigger, list, plugins, config) {
- const availableTrigger = typeof trigger === 'string' ? document.getElementById(trigger) : trigger;
+ const availableTrigger =
+ typeof trigger === 'string' ? document.getElementById(trigger) : trigger;
this.hooks.forEach((hook, i) => {
const aHook = hook;
diff --git a/app/assets/javascripts/droplab/keyboard.js b/app/assets/javascripts/droplab/keyboard.js
index 02f1b805ce4..40837ffdf8f 100644
--- a/app/assets/javascripts/droplab/keyboard.js
+++ b/app/assets/javascripts/droplab/keyboard.js
@@ -2,15 +2,18 @@
import { ACTIVE_CLASS } from './constants';
-const Keyboard = function () {
+const Keyboard = function() {
var currentKey;
var currentFocus;
var isUpArrow = false;
var isDownArrow = false;
var removeHighlight = function removeHighlight(list) {
- var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider):not(.hidden)'), 0);
+ var itemElements = Array.prototype.slice.call(
+ list.list.querySelectorAll('li:not(.divider):not(.hidden)'),
+ 0,
+ );
var listItems = [];
- for(var i = 0; i < itemElements.length; i++) {
+ for (var i = 0; i < itemElements.length; i++) {
var listItem = itemElements[i];
listItem.classList.remove(ACTIVE_CLASS);
@@ -23,13 +26,13 @@ const Keyboard = function () {
var setMenuForArrows = function setMenuForArrows(list) {
var listItems = removeHighlight(list);
- if(list.currentIndex>0){
- if(!listItems[list.currentIndex-1]){
- list.currentIndex = list.currentIndex-1;
+ if (list.currentIndex > 0) {
+ if (!listItems[list.currentIndex - 1]) {
+ list.currentIndex = list.currentIndex - 1;
}
- if (listItems[list.currentIndex-1]) {
- var el = listItems[list.currentIndex-1];
+ if (listItems[list.currentIndex - 1]) {
+ var el = listItems[list.currentIndex - 1];
var filterDropdownEl = el.closest('.filter-dropdown');
el.classList.add(ACTIVE_CLASS);
@@ -55,7 +58,7 @@ const Keyboard = function () {
};
var selectItem = function selectItem(list) {
var listItems = removeHighlight(list);
- var currentItem = listItems[list.currentIndex-1];
+ var currentItem = listItems[list.currentIndex - 1];
var listEvent = new CustomEvent('click.dl', {
detail: {
list: list,
@@ -65,43 +68,49 @@ const Keyboard = function () {
});
list.list.dispatchEvent(listEvent);
list.hide();
- }
+ };
- var keydown = function keydown(e){
+ var keydown = function keydown(e) {
var typedOn = e.target;
var list = e.detail.hook.list;
var currentIndex = list.currentIndex;
isUpArrow = false;
isDownArrow = false;
- if(e.detail.which){
+ if (e.detail.which) {
currentKey = e.detail.which;
- if(currentKey === 13){
+ if (currentKey === 13) {
selectItem(e.detail.hook.list);
return;
}
- if(currentKey === 38) {
+ if (currentKey === 38) {
isUpArrow = true;
}
- if(currentKey === 40) {
+ if (currentKey === 40) {
isDownArrow = true;
}
- } else if(e.detail.key) {
+ } else if (e.detail.key) {
currentKey = e.detail.key;
- if(currentKey === 'Enter'){
+ if (currentKey === 'Enter') {
selectItem(e.detail.hook.list);
return;
}
- if(currentKey === 'ArrowUp') {
+ if (currentKey === 'ArrowUp') {
isUpArrow = true;
}
- if(currentKey === 'ArrowDown') {
+ if (currentKey === 'ArrowDown') {
isDownArrow = true;
}
}
- if(isUpArrow){ currentIndex--; }
- if(isDownArrow){ currentIndex++; }
- if(currentIndex < 0){ currentIndex = 0; }
+ if (isUpArrow) {
+ currentIndex--;
+ }
+ if (isDownArrow) {
+ currentIndex++;
+ }
+ if (currentIndex < 0) {
+ currentIndex = 0;
+ }
list.currentIndex = currentIndex;
setMenuForArrows(e.detail.hook.list);
};
diff --git a/app/assets/javascripts/droplab/plugins/ajax.js b/app/assets/javascripts/droplab/plugins/ajax.js
index 267b53fa4f2..48b2a90c459 100644
--- a/app/assets/javascripts/droplab/plugins/ajax.js
+++ b/app/assets/javascripts/droplab/plugins/ajax.js
@@ -43,12 +43,12 @@ const Ajax = {
return AjaxCache.retrieve(config.endpoint)
.then(self.preprocessing.bind(null, config))
- .then((data) => self._loadData(data, config, self))
+ .then(data => self._loadData(data, config, self))
.catch(config.onError);
},
destroy: function() {
this.destroyed = true;
- }
+ },
};
export default Ajax;
diff --git a/app/assets/javascripts/droplab/plugins/ajax_filter.js b/app/assets/javascripts/droplab/plugins/ajax_filter.js
index 1db20227a16..66a52548417 100644
--- a/app/assets/javascripts/droplab/plugins/ajax_filter.js
+++ b/app/assets/javascripts/droplab/plugins/ajax_filter.js
@@ -41,8 +41,10 @@ const AjaxFilter = {
if (config.searchValueFunction) {
searchValue = config.searchValueFunction();
}
- if (config.loadingTemplate && this.hook.list.data === undefined ||
- this.hook.list.data.length === 0) {
+ if (
+ (config.loadingTemplate && this.hook.list.data === undefined) ||
+ this.hook.list.data.length === 0
+ ) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
@@ -61,7 +63,7 @@ const AjaxFilter = {
params[config.searchKey] = searchValue;
var url = config.endpoint + this.buildParams(params);
return AjaxCache.retrieve(url)
- .then((data) => {
+ .then(data => {
this._loadData(data, config);
if (config.onLoadingFinished) {
config.onLoadingFinished(data);
@@ -72,8 +74,7 @@ const AjaxFilter = {
_loadData(data, config) {
const list = this.hook.list;
- if (config.loadingTemplate && list.data === undefined ||
- list.data.length === 0) {
+ if ((config.loadingTemplate && list.data === undefined) || list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = this.listTemplate;
@@ -81,7 +82,8 @@ const AjaxFilter = {
}
if (!this.destroyed) {
var hookListChildren = list.list.children;
- var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
+ var onlyDynamicList =
+ hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
list.hide();
}
@@ -100,12 +102,12 @@ const AjaxFilter = {
},
destroy: function destroy() {
- if (this.timeout)clearTimeout(this.timeout);
+ if (this.timeout) clearTimeout(this.timeout);
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceTrigger);
this.hook.trigger.removeEventListener('focus', this.eventWrapper.debounceTrigger);
- }
+ },
};
export default AjaxFilter;
diff --git a/app/assets/javascripts/droplab/plugins/filter.js b/app/assets/javascripts/droplab/plugins/filter.js
index 404d707cf7a..6f1dc252d24 100644
--- a/app/assets/javascripts/droplab/plugins/filter.js
+++ b/app/assets/javascripts/droplab/plugins/filter.js
@@ -1,7 +1,7 @@
/* eslint-disable */
const Filter = {
- keydown: function(e){
+ keydown: function(e) {
if (this.destroyed) return;
var hiddenCount = 0;
@@ -14,14 +14,14 @@ const Filter = {
var matches = [];
var filterFunction;
// will only work on dynamically set data
- if(!data){
+ if (!data) {
return;
}
if (config && config.filterFunction && typeof config.filterFunction === 'function') {
filterFunction = config.filterFunction;
} else {
- filterFunction = function(o){
+ filterFunction = function(o) {
// cheap string search
o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
return o;
@@ -47,20 +47,23 @@ const Filter = {
},
debounceKeydown: function debounceKeydown(e) {
- if ([
- 13, // enter
- 16, // shift
- 17, // ctrl
- 18, // alt
- 20, // caps lock
- 37, // left arrow
- 38, // up arrow
- 39, // right arrow
- 40, // down arrow
- 91, // left window
- 92, // right window
- 93, // select
- ].indexOf(e.detail.which || e.detail.keyCode) > -1) return;
+ if (
+ [
+ 13, // enter
+ 16, // shift
+ 17, // ctrl
+ 18, // alt
+ 20, // caps lock
+ 37, // left arrow
+ 38, // up arrow
+ 39, // right arrow
+ 40, // down arrow
+ 91, // left window
+ 92, // right window
+ 93, // select
+ ].indexOf(e.detail.which || e.detail.keyCode) > -1
+ )
+ return;
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(this.keydown.bind(this, e), 200);
@@ -87,7 +90,7 @@ const Filter = {
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.removeEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
- }
+ },
};
export default Filter;
diff --git a/app/assets/javascripts/droplab/plugins/input_setter.js b/app/assets/javascripts/droplab/plugins/input_setter.js
index d01fbc5830d..6cfc738a1e3 100644
--- a/app/assets/javascripts/droplab/plugins/input_setter.js
+++ b/app/assets/javascripts/droplab/plugins/input_setter.js
@@ -36,8 +36,8 @@ const InputSetter = {
const inputAttribute = config.inputAttribute;
if (input.hasAttribute(inputAttribute)) return input.setAttribute(inputAttribute, newValue);
- if (input.tagName === 'INPUT') return input.value = newValue;
- return input.textContent = newValue;
+ if (input.tagName === 'INPUT') return (input.value = newValue);
+ return (input.textContent = newValue);
},
destroy() {
diff --git a/app/assets/javascripts/droplab/utils.js b/app/assets/javascripts/droplab/utils.js
index bfe056a0fcc..5272778ce2d 100644
--- a/app/assets/javascripts/droplab/utils.js
+++ b/app/assets/javascripts/droplab/utils.js
@@ -5,7 +5,12 @@ import { DATA_TRIGGER, DATA_DROPDOWN, TEMPLATE_REGEX } from './constants';
const utils = {
toCamelCase(attr) {
- return this.camelize(attr.split('-').slice(1).join(' '));
+ return this.camelize(
+ attr
+ .split('-')
+ .slice(1)
+ .join(' '),
+ );
},
template(templateString, data) {
@@ -17,9 +22,11 @@ const utils = {
},
camelize(str) {
- return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
- return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
- }).replace(/\s+/g, '');
+ return str
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
+ return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
+ })
+ .replace(/\s+/g, '');
},
closest(thisTag, stopTag) {
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index d2778bcdf1c..9987fbcb6a7 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -136,7 +136,7 @@ export default function dropzoneInput(form) {
// removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue.
- $cancelButton.on('click', (e) => {
+ $cancelButton.on('click', e => {
e.preventDefault();
e.stopPropagation();
Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
@@ -146,8 +146,10 @@ export default function dropzoneInput(form) {
// clear dropzone files queue, change status of failed files to undefined,
// and add that files to the dropzone files queue again.
// addFile() adds file to dropzone files queue and upload it.
- $retryLink.on('click', (e) => {
- const dropzoneInstance = Dropzone.forElement(e.target.closest('.js-main-target-form').querySelector('.div-dropzone'));
+ $retryLink.on('click', e => {
+ const dropzoneInstance = Dropzone.forElement(
+ e.target.closest('.js-main-target-form').querySelector('.div-dropzone'),
+ );
const failedFiles = dropzoneInstance.files;
e.preventDefault();
@@ -156,7 +158,7 @@ export default function dropzoneInput(form) {
// uploading of files that are being uploaded at the moment.
dropzoneInstance.removeAllFiles(true);
- failedFiles.map((failedFile) => {
+ failedFiles.map(failedFile => {
const file = failedFile;
if (file.status === Dropzone.ERROR) {
@@ -168,7 +170,7 @@ export default function dropzoneInput(form) {
});
});
// eslint-disable-next-line consistent-return
- handlePaste = (event) => {
+ handlePaste = event => {
const pasteEvent = event.originalEvent;
if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
const image = isImage(pasteEvent);
@@ -182,7 +184,7 @@ export default function dropzoneInput(form) {
}
};
- isImage = (data) => {
+ isImage = data => {
let i = 0;
while (i < data.clipboardData.items.length) {
const item = data.clipboardData.items[i];
@@ -203,8 +205,12 @@ export default function dropzoneInput(form) {
const caretStart = textarea.selectionStart;
const caretEnd = textarea.selectionEnd;
const textEnd = $(child).val().length;
- const beforeSelection = $(child).val().substring(0, caretStart);
- const afterSelection = $(child).val().substring(caretEnd, textEnd);
+ const beforeSelection = $(child)
+ .val()
+ .substring(0, caretStart);
+ const afterSelection = $(child)
+ .val()
+ .substring(caretEnd, textEnd);
$(child).val(beforeSelection + formattedText + afterSelection);
textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`;
@@ -212,11 +218,11 @@ export default function dropzoneInput(form) {
return formTextarea.trigger('input');
};
- addFileToForm = (path) => {
+ addFileToForm = path => {
$(form).append(`<input type="hidden" name="files[]" value="${_.escape(path)}">`);
};
- getFilename = (e) => {
+ getFilename = e => {
let value;
if (window.clipboardData && window.clipboardData.getData) {
value = window.clipboardData.getData('Text');
@@ -231,7 +237,7 @@ export default function dropzoneInput(form) {
const closeSpinner = () => $uploadingProgressContainer.addClass('hide');
- const showError = (message) => {
+ const showError = message => {
$uploadingErrorContainer.removeClass('hide');
$uploadingErrorMessage.html(message);
};
@@ -252,14 +258,15 @@ export default function dropzoneInput(form) {
showSpinner();
closeAlertMessage();
- axios.post(uploadsPath, formData)
+ axios
+ .post(uploadsPath, formData)
.then(({ data }) => {
const md = data.link.markdown;
insertToTextArea(filename, md);
closeSpinner();
})
- .catch((e) => {
+ .catch(e => {
showError(e.response.data.message);
closeSpinner();
});
@@ -267,7 +274,8 @@ export default function dropzoneInput(form) {
updateAttachingMessage = (files, messageContainer) => {
let attachingMessage;
- const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued').length;
+ const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued')
+ .length;
// Dinamycally change uploading files text depending on files number in
// dropzone files queue.
@@ -282,7 +290,10 @@ export default function dropzoneInput(form) {
form.find('.markdown-selector').click(function onMarkdownClick(e) {
e.preventDefault();
- $(this).closest('.gfm-form').find('.div-dropzone').click();
+ $(this)
+ .closest('.gfm-form')
+ .find('.div-dropzone')
+ .click();
formTextarea.focus();
});
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index c7b5a35cc14..dbfcf8cc921 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -3,8 +3,7 @@ import Pikaday from 'pikaday';
import dateFormat from 'dateformat';
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 { timeFor, parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
import boardsStore from './boards/stores/boards_store';
class DueDateSelect {
diff --git a/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
index e9defb62cf8..c5f9fcf6358 100644
--- a/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
+++ b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js
@@ -13,9 +13,11 @@ const rainbowCodePoint = 127752; // parseInt('1F308', 16)
function isRainbowFlagEmoji(emojiUnicode) {
const characters = Array.from(emojiUnicode);
// Length 4 because flags are made of 2 characters which are surrogate pairs
- return emojiUnicode.length === 4 &&
+ return (
+ emojiUnicode.length === 4 &&
characters[0].codePointAt(0) === baseFlagCodePoint &&
- characters[1].codePointAt(0) === rainbowCodePoint;
+ characters[1].codePointAt(0) === rainbowCodePoint
+ );
}
// Chrome <57 renders keycaps oddly
@@ -26,22 +28,28 @@ function isKeycapEmoji(emojiUnicode) {
}
// Check for a skin tone variation emoji which aren't always supported
-const tone1 = 127995;// parseInt('1F3FB', 16)
-const tone5 = 127999;// parseInt('1F3FF', 16)
+const tone1 = 127995; // parseInt('1F3FB', 16)
+const tone5 = 127999; // parseInt('1F3FF', 16)
function isSkinToneComboEmoji(emojiUnicode) {
- return emojiUnicode.length > 2 && Array.from(emojiUnicode).some((char) => {
- const cp = char.codePointAt(0);
- return cp >= tone1 && cp <= tone5;
- });
+ return (
+ emojiUnicode.length > 2 &&
+ Array.from(emojiUnicode).some(char => {
+ const cp = char.codePointAt(0);
+ return cp >= tone1 && cp <= tone5;
+ })
+ );
}
// macOS supports most skin tone emoji's but
// doesn't support the skin tone versions of horse racing
-const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16)
+const horseRacingCodePoint = 127943; // parseInt('1F3C7', 16)
function isHorceRacingSkinToneComboEmoji(emojiUnicode) {
const firstCharacter = Array.from(emojiUnicode)[0];
- return firstCharacter && firstCharacter.codePointAt(0) === horseRacingCodePoint &&
- isSkinToneComboEmoji(emojiUnicode);
+ return (
+ firstCharacter &&
+ firstCharacter.codePointAt(0) === horseRacingCodePoint &&
+ isSkinToneComboEmoji(emojiUnicode)
+ );
}
// Check for `family_*`, `kiss_*`, `couple_*`
@@ -52,7 +60,7 @@ const personEndCodePoint = 128105; // parseInt('1F469', 16)
function isPersonZwjEmoji(emojiUnicode) {
let hasPersonEmoji = false;
let hasZwj = false;
- Array.from(emojiUnicode).forEach((character) => {
+ Array.from(emojiUnicode).forEach(character => {
const cp = character.codePointAt(0);
if (cp === zwj) {
hasZwj = true;
@@ -80,10 +88,7 @@ function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) {
// in `isEmojiUnicodeSupported` logic
function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) {
const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode);
- return (
- (unicodeSupportMap.skinToneModifier && isSkinToneResult) ||
- !isSkinToneResult
- );
+ return (unicodeSupportMap.skinToneModifier && isSkinToneResult) || !isSkinToneResult;
}
// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice
@@ -91,8 +96,7 @@ function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) {
function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) {
const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode);
return (
- (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) ||
- !isHorseRacingSkinToneResult
+ (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || !isHorseRacingSkinToneResult
);
}
@@ -100,10 +104,7 @@ function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnico
// in `isEmojiUnicodeSupported` logic
function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) {
const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode);
- return (
- (unicodeSupportMap.personZwj && isPersonZwjResult) ||
- !isPersonZwjResult
- );
+ return (unicodeSupportMap.personZwj && isPersonZwjResult) || !isPersonZwjResult;
}
// Takes in a support map and determines whether
@@ -111,16 +112,20 @@ function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) {
//
// Combines all the edge case tests into a one-stop shop method
function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) {
- const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome &&
+ const isOlderThanChrome57 =
+ unicodeSupportMap.meta &&
+ unicodeSupportMap.meta.isChrome &&
unicodeSupportMap.meta.chromeVersion < 57;
// For comments about each scenario, see the comments above each individual respective function
- return unicodeSupportMap[unicodeVersion] &&
+ return (
+ unicodeSupportMap[unicodeVersion] &&
!(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) &&
checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) &&
checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) &&
checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) &&
- checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode);
+ checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode)
+ );
}
export {
diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue
index 9de851c9409..00d197d294f 100644
--- a/app/assets/javascripts/environments/components/container.vue
+++ b/app/assets/javascripts/environments/components/container.vue
@@ -1,40 +1,40 @@
<script>
- import tablePagination from '../../vue_shared/components/table_pagination.vue';
- import environmentTable from '../components/environments_table.vue';
+import tablePagination from '../../vue_shared/components/table_pagination.vue';
+import environmentTable from '../components/environments_table.vue';
- export default {
- components: {
- environmentTable,
- tablePagination,
+export default {
+ components: {
+ environmentTable,
+ tablePagination,
+ },
+ props: {
+ isLoading: {
+ type: Boolean,
+ required: true,
},
- props: {
- isLoading: {
- type: Boolean,
- required: true,
- },
- environments: {
- type: Array,
- required: true,
- },
- pagination: {
- type: Object,
- required: true,
- },
- canCreateDeployment: {
- type: Boolean,
- required: true,
- },
- canReadEnvironment: {
- type: Boolean,
- required: true,
- },
+ environments: {
+ type: Array,
+ required: true,
},
- methods: {
- onChangePage(page) {
- this.$emit('onChangePage', page);
- },
+ pagination: {
+ type: Object,
+ required: true,
},
- };
+ canCreateDeployment: {
+ type: Boolean,
+ required: true,
+ },
+ canReadEnvironment: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ methods: {
+ onChangePage(page) {
+ this.$emit('onChangePage', page);
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue
index cf78f89981e..2360a52645b 100644
--- a/app/assets/javascripts/environments/components/empty_state.vue
+++ b/app/assets/javascripts/environments/components/empty_state.vue
@@ -1,44 +1,45 @@
<script>
- export default {
- name: 'EnvironmentsEmptyState',
- props: {
- newPath: {
- type: String,
- required: true,
- },
- canCreateEnvironment: {
- type: Boolean,
- required: true,
- },
- helpPath: {
- type: String,
- required: true,
- },
+export default {
+ name: 'EnvironmentsEmptyState',
+ props: {
+ newPath: {
+ type: String,
+ required: true,
},
- };
+ canCreateEnvironment: {
+ type: Boolean,
+ required: true,
+ },
+ helpPath: {
+ type: String,
+ required: true,
+ },
+ },
+};
</script>
<template>
- <div class="blank-state-row">
- <div class="blank-state-center">
- <h2 class="blank-state-title js-blank-state-title">
- {{ s__("Environments|You don't have any environments right now.") }}
- </h2>
+ <div class="empty-state">
+ <div class="text-content">
+ <h4 class="blank-state-title js-blank-state-title">
+ {{ s__("Environments|You don't have any environments right now") }}
+ </h4>
<p class="blank-state-text">
{{ s__(`Environments|Environments are places where
-code gets deployed, such as staging or production.`) }}
- <br />
+ code gets deployed, such as staging or production.`) }}
<a :href="helpPath">
{{ s__("Environments|Read more about environments") }}
</a>
</p>
- <a
- v-if="canCreateEnvironment"
- :href="newPath"
- class="btn btn-success js-new-environment-button"
- >
- {{ s__("Environments|New environment") }}
- </a>
+ <div class="text-center">
+ <a
+ v-if="canCreateEnvironment"
+ :href="newPath"
+ class="btn btn-success js-new-environment-button"
+ >
+ {{ s__("Environments|New environment") }}
+ </a>
+ </div>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue
index efbf88d0f11..9e137f79dcc 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.vue
+++ b/app/assets/javascripts/environments/components/environment_rollback.vue
@@ -38,7 +38,9 @@ export default {
computed: {
title() {
- return this.isLastDeployment ? s__('Environments|Re-deploy to environment') : s__('Environments|Rollback environment');
+ return this.isLastDeployment
+ ? s__('Environments|Re-deploy to environment')
+ : s__('Environments|Rollback environment');
},
},
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index de0fbdb2e91..f044d31c776 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -5,31 +5,32 @@ import Translate from '../../vue_shared/translate';
Vue.use(Translate);
-export default () => new Vue({
- el: '#environments-folder-list-view',
- components: {
- environmentsFolderApp,
- },
- data() {
- const environmentsData = document.querySelector(this.$options.el).dataset;
+export default () =>
+ new Vue({
+ el: '#environments-folder-list-view',
+ components: {
+ environmentsFolderApp,
+ },
+ data() {
+ const environmentsData = document.querySelector(this.$options.el).dataset;
- return {
- endpoint: environmentsData.endpoint,
- folderName: environmentsData.folderName,
- cssContainerClass: environmentsData.cssClass,
- canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
- canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
- };
- },
- render(createElement) {
- return createElement('environments-folder-app', {
- props: {
- endpoint: this.endpoint,
- folderName: this.folderName,
- cssContainerClass: this.cssContainerClass,
- canCreateDeployment: this.canCreateDeployment,
- canReadEnvironment: this.canReadEnvironment,
- },
- });
- },
-});
+ return {
+ endpoint: environmentsData.endpoint,
+ folderName: environmentsData.folderName,
+ cssContainerClass: environmentsData.cssClass,
+ canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
+ canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
+ };
+ },
+ render(createElement) {
+ return createElement('environments-folder-app', {
+ props: {
+ endpoint: this.endpoint,
+ folderName: this.folderName,
+ cssContainerClass: this.cssContainerClass,
+ canCreateDeployment: this.canCreateDeployment,
+ canReadEnvironment: this.canReadEnvironment,
+ },
+ });
+ },
+ });
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index e69bfa0b2cc..6be4845fe4c 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -1,46 +1,43 @@
<script>
- import environmentsMixin from '../mixins/environments_mixin';
- import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
- import StopEnvironmentModal from '../components/stop_environment_modal.vue';
+import environmentsMixin from '../mixins/environments_mixin';
+import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
+import StopEnvironmentModal from '../components/stop_environment_modal.vue';
- export default {
- components: {
- StopEnvironmentModal,
- },
+export default {
+ components: {
+ StopEnvironmentModal,
+ },
- mixins: [
- environmentsMixin,
- CIPaginationMixin,
- ],
+ mixins: [environmentsMixin, CIPaginationMixin],
- props: {
- endpoint: {
- type: String,
- required: true,
- },
- folderName: {
- type: String,
- required: true,
- },
- cssContainerClass: {
- type: String,
- required: true,
- },
- canCreateDeployment: {
- type: Boolean,
- required: true,
- },
- canReadEnvironment: {
- type: Boolean,
- required: true,
- },
+ props: {
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ folderName: {
+ type: String,
+ required: true,
+ },
+ cssContainerClass: {
+ type: String,
+ required: true,
+ },
+ canCreateDeployment: {
+ type: Boolean,
+ required: true,
+ },
+ canReadEnvironment: {
+ type: Boolean,
+ required: true,
},
- methods: {
- successCallback(resp) {
- this.saveData(resp);
- },
+ },
+ methods: {
+ successCallback(resp) {
+ this.saveData(resp);
},
- };
+ },
+};
</script>
<template>
<div :class="cssContainerClass">
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index afc4aba6554..5b6833fb15d 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -5,35 +5,36 @@ import Translate from '../vue_shared/translate';
Vue.use(Translate);
-export default () => new Vue({
- el: '#environments-list-view',
- components: {
- environmentsComponent,
- },
- data() {
- const environmentsData = document.querySelector(this.$options.el).dataset;
+export default () =>
+ new Vue({
+ el: '#environments-list-view',
+ components: {
+ environmentsComponent,
+ },
+ data() {
+ const environmentsData = document.querySelector(this.$options.el).dataset;
- return {
- endpoint: environmentsData.environmentsDataEndpoint,
- newEnvironmentPath: environmentsData.newEnvironmentPath,
- helpPagePath: environmentsData.helpPagePath,
- cssContainerClass: environmentsData.cssClass,
- canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
- canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
- canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
- };
- },
- render(createElement) {
- return createElement('environments-component', {
- props: {
- endpoint: this.endpoint,
- newEnvironmentPath: this.newEnvironmentPath,
- helpPagePath: this.helpPagePath,
- cssContainerClass: this.cssContainerClass,
- canCreateEnvironment: this.canCreateEnvironment,
- canCreateDeployment: this.canCreateDeployment,
- canReadEnvironment: this.canReadEnvironment,
- },
- });
- },
-});
+ return {
+ endpoint: environmentsData.environmentsDataEndpoint,
+ newEnvironmentPath: environmentsData.newEnvironmentPath,
+ helpPagePath: environmentsData.helpPagePath,
+ cssContainerClass: environmentsData.cssClass,
+ canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
+ canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
+ canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
+ };
+ },
+ render(createElement) {
+ return createElement('environments-component', {
+ props: {
+ endpoint: this.endpoint,
+ newEnvironmentPath: this.newEnvironmentPath,
+ helpPagePath: this.helpPagePath,
+ cssContainerClass: this.cssContainerClass,
+ canCreateEnvironment: this.canCreateEnvironment,
+ canCreateDeployment: this.canCreateDeployment,
+ canReadEnvironment: this.canReadEnvironment,
+ },
+ });
+ },
+ });
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index d71964612c5..96dc1f07cb9 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -4,9 +4,7 @@
import _ from 'underscore';
import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll';
-import {
- getParameterByName,
-} from '../../lib/utils/common_utils';
+import { getParameterByName } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
import Flash from '../../flash';
import eventHub from '../event_hub';
@@ -19,7 +17,6 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue';
import container from '../components/container.vue';
export default {
-
components: {
environmentTable,
container,
@@ -65,7 +62,8 @@ export default {
updateContent(parameters) {
this.updateInternalState(parameters);
// fetch new data
- return this.service.fetchEnvironments(this.requestData)
+ return this.service
+ .fetchEnvironments(this.requestData)
.then(response => this.successCallback(response))
.then(() => {
// restart polling
@@ -88,7 +86,8 @@ export default {
if (!this.isMakingRequest) {
this.isLoading = true;
- this.service.postAction(endpoint)
+ this.service
+ .postAction(endpoint)
.then(() => this.fetchEnvironments())
.catch(() => {
this.isLoading = false;
@@ -100,7 +99,8 @@ export default {
fetchEnvironments() {
this.isLoading = true;
- return this.service.fetchEnvironments(this.requestData)
+ return this.service
+ .fetchEnvironments(this.requestData)
.then(this.successCallback)
.catch(this.errorCallback);
},
@@ -111,7 +111,9 @@ export default {
stopEnvironment(environment) {
const endpoint = environment.stop_path;
- const errorMessage = s__('Environments|An error occurred while stopping the environment, please try again');
+ const errorMessage = s__(
+ 'Environments|An error occurred while stopping the environment, please try again',
+ );
this.postAction({ endpoint, errorMessage });
},
},
@@ -149,7 +151,7 @@ export default {
data: this.requestData,
successCallback: this.successCallback,
errorCallback: this.errorCallback,
- notificationCallback: (isMakingRequest) => {
+ notificationCallback: isMakingRequest => {
this.isMakingRequest = isMakingRequest;
},
});
diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js
index 1d60847147b..42b3fb8c6da 100644
--- a/app/assets/javascripts/experimental_flags.js
+++ b/app/assets/javascripts/experimental_flags.js
@@ -2,7 +2,7 @@ import $ from 'jquery';
import Cookies from 'js-cookie';
export default () => {
- $('.js-experiment-feature-toggle').on('change', (e) => {
+ $('.js-experiment-feature-toggle').on('change', e => {
const el = e.target;
Cookies.set(el.name, el.value, {
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js
index 03dfa942d69..173fe7c69de 100644
--- a/app/assets/javascripts/feature_highlight/feature_highlight.js
+++ b/app/assets/javascripts/feature_highlight/feature_highlight.js
@@ -1,13 +1,6 @@
import $ from 'jquery';
-import {
- getSelector,
- inserted,
-} from './feature_highlight_helper';
-import {
- togglePopover,
- mouseenter,
- debouncedMouseleave,
-} from '../shared/popover';
+import { getSelector, inserted } from './feature_highlight_helper';
+import { togglePopover, mouseenter, debouncedMouseleave } from '../shared/popover';
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const $selector = $(getSelector(id));
@@ -41,8 +34,9 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
export function findHighestPriorityFeature() {
let priorityFeature;
- const sortedFeatureEls = [].slice.call(document.querySelectorAll('.js-feature-highlight')).sort((a, b) =>
- (a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
+ const sortedFeatureEls = [].slice
+ .call(document.querySelectorAll('.js-feature-highlight'))
+ .sort((a, b) => (a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
const [priorityFeatureEl] = sortedFeatureEls;
if (priorityFeatureEl) {
diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js
index d5b97ebb264..fd9433b625c 100644
--- a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js
+++ b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js
@@ -8,10 +8,17 @@ import { togglePopover } from '../shared/popover';
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export function dismiss(highlightId) {
- axios.post(this.attr('data-dismiss-endpoint'), {
- feature_name: highlightId,
- })
- .catch(() => Flash(__('An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.')));
+ axios
+ .post(this.attr('data-dismiss-endpoint'), {
+ feature_name: highlightId,
+ })
+ .catch(() =>
+ Flash(
+ __(
+ 'An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.',
+ ),
+ ),
+ );
togglePopover.call(this, false);
this.hide();
@@ -23,8 +30,7 @@ export function inserted() {
const $popover = $(this);
const dismissWrapper = dismiss.bind($popover, highlightId);
- $(`#${popoverId} .dismiss-feature-highlight`)
- .on('click', dismissWrapper);
+ $(`#${popoverId} .dismiss-feature-highlight`).on('click', dismissWrapper);
const lazyImg = $(`#${popoverId} .feature-highlight-illustration`)[0];
if (lazyImg) {
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index 6a4874e1ab8..3233f5c4f71 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -25,13 +25,15 @@ export default {
if (!this.userCanCreateNote) {
// data-can-create-note is an empty string when true, otherwise undefined
- this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('canCreateNote') === '';
+ this.userCanCreateNote =
+ $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('canCreateNote') === '';
}
this.isParallelView = Cookies.get('diff_view') === 'parallel';
if (this.userCanCreateNote) {
- $diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
+ $diffFile
+ .on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
.on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e));
}
},
@@ -64,9 +66,11 @@ export default {
},
validateButtonParent(buttonParentElement) {
- return !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) &&
+ return (
+ !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) &&
!buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) &&
!buttonParentElement.classList.contains(NO_COMMENT_CLASS) &&
- !buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS);
+ !buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS)
+ );
},
};
diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js
index b17ba3c21db..64b09c8b62c 100644
--- a/app/assets/javascripts/filterable_list.js
+++ b/app/assets/javascripts/filterable_list.js
@@ -65,12 +65,15 @@ export default class FilterableList {
this.isBusy = true;
- return axios.get(this.getFilterEndpoint(), {
- params,
- }).then((res) => {
- this.onFilterSuccess(res, params);
- this.onFilterComplete();
- }).catch(() => this.onFilterComplete());
+ return axios
+ .get(this.getFilterEndpoint(), {
+ params,
+ })
+ .then(res => {
+ this.onFilterSuccess(res, params);
+ this.onFilterComplete();
+ })
+ .catch(() => this.onFilterComplete());
}
onFilterSuccess(response, queryData) {
@@ -81,9 +84,13 @@ export default class FilterableList {
// Change url so if user reload a page - search results are saved
const currentPath = this.getPagePath(queryData);
- return window.history.replaceState({
- page: currentPath,
- }, document.title, currentPath);
+ return window.history.replaceState(
+ {
+ page: currentPath,
+ },
+ document.title,
+ currentPath,
+ );
}
onFilterComplete() {
diff --git a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
index d7aa4ce597f..934375023ba 100644
--- a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js
@@ -1,20 +1,23 @@
import FilteredSearchTokenKeys from './filtered_search_token_keys';
-const tokenKeys = [{
- key: 'status',
- type: 'string',
- param: 'status',
- symbol: '',
- icon: 'messages',
- tag: 'status',
-}, {
- key: 'type',
- type: 'string',
- param: 'type',
- symbol: '',
- icon: 'cube',
- tag: 'type',
-}];
+const tokenKeys = [
+ {
+ key: 'status',
+ type: 'string',
+ param: 'status',
+ symbol: '',
+ icon: 'messages',
+ tag: 'status',
+ },
+ {
+ key: 'type',
+ type: 'string',
+ param: 'type',
+ symbol: '',
+ icon: 'cube',
+ tag: 'type',
+ },
+];
const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
diff --git a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
index 21b5ccdb613..b9bc5e6ed7f 100644
--- a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
+++ b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
@@ -21,9 +21,11 @@ export default {
},
computed: {
processedItems() {
- return this.items.map((item) => {
- const { tokens, searchToken }
- = FilteredSearchTokenizer.processTokens(item, this.allowedKeys);
+ return this.items.map(item => {
+ const { tokens, searchToken } = FilteredSearchTokenizer.processTokens(
+ item,
+ this.allowedKeys,
+ );
const resultantTokens = tokens.map(token => ({
prefix: `${token.key}:`,
diff --git a/app/assets/javascripts/filtered_search/dropdown_emoji.js b/app/assets/javascripts/filtered_search/dropdown_emoji.js
index 5ddd0e5e690..af7936a92fb 100644
--- a/app/assets/javascripts/filtered_search/dropdown_emoji.js
+++ b/app/assets/javascripts/filtered_search/dropdown_emoji.js
@@ -24,8 +24,12 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
};
import(/* webpackChunkName: 'emoji' */ '~/emoji')
- .then(({ glEmojiTag }) => { this.glEmojiTag = glEmojiTag; })
- .catch(() => { /* ignore error and leave emoji name in the search bar */ });
+ .then(({ glEmojiTag }) => {
+ this.glEmojiTag = glEmojiTag;
+ })
+ .catch(() => {
+ /* ignore error and leave emoji name in the search bar */
+ });
this.unbindEvents();
this.bindEvents();
@@ -48,7 +52,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
}
itemClicked(e) {
- super.itemClicked(e, (selected) => {
+ super.itemClicked(e, selected => {
const name = selected.querySelector('.js-data-value').innerText.trim();
return DropdownUtils.getEscapedText(name);
});
@@ -64,7 +68,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
// Replace empty gl-emoji tag to real content
const dropdownItems = [...this.dropdown.querySelectorAll('.filter-dropdown-item')];
- dropdownItems.forEach((dropdownItem) => {
+ dropdownItems.forEach(dropdownItem => {
const name = dropdownItem.querySelector('.js-data-value').innerText;
const emojiTag = this.glEmojiTag(name);
const emojiElement = dropdownItem.querySelector('gl-emoji');
@@ -73,7 +77,6 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
}
init() {
- this.droplab
- .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
+ this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
}
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js
index c568f4e4ebf..1a1135ae929 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
@@ -41,8 +41,10 @@ export default class DropdownHint extends FilteredSearchDropdown {
previousInputValues.forEach((value, index) => {
searchTerms.push(value);
- if (index === previousInputValues.length - 1
- && token.indexOf(value.toLowerCase()) !== -1) {
+ if (
+ index === previousInputValues.length - 1 &&
+ token.indexOf(value.toLowerCase()) !== -1
+ ) {
searchTerms.pop();
}
});
@@ -64,13 +66,12 @@ export default class DropdownHint extends FilteredSearchDropdown {
}
renderContent() {
- const dropdownData = this.tokenKeys.get()
- .map(tokenKey => ({
- icon: `${gon.sprite_icons}#${tokenKey.icon}`,
- hint: tokenKey.key,
- tag: `:${tokenKey.tag}`,
- type: tokenKey.type,
- }));
+ const dropdownData = this.tokenKeys.get().map(tokenKey => ({
+ icon: `${gon.sprite_icons}#${tokenKey.icon}`,
+ hint: tokenKey.key,
+ tag: `:${tokenKey.tag}`,
+ type: tokenKey.type,
+ }));
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index 2ffda7e2037..0264f934914 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
@@ -29,20 +29,18 @@ export default class DropdownNonUser extends FilteredSearchDropdown {
}
itemClicked(e) {
- super.itemClicked(e, (selected) => {
+ super.itemClicked(e, selected => {
const title = selected.querySelector('.js-data-value').innerText.trim();
return `${this.symbol}${DropdownUtils.getEscapedText(title)}`;
});
}
renderContent(forceShowList = false) {
- this.droplab
- .changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
+ this.droplab.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList);
}
init() {
- this.droplab
- .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
+ this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
}
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index 6da6ca10008..1b79a3320c6 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -41,7 +41,7 @@ export default class DropdownUtils {
// Removes the first character if it is a quotation so that we can search
// with multiple words
- if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
+ if ((value[0] === '"' || value[0] === "'") && title.indexOf(' ') !== -1) {
value = value.slice(1);
}
@@ -82,11 +82,13 @@ export default class DropdownUtils {
// Reduce the colors to 4
colors.length = Math.min(colors.length, 4);
- const color = colors.map((c, i) => {
- const percentFirst = Math.floor(spacing * i);
- const percentSecond = Math.floor(spacing * (i + 1));
- return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
- }).join(', ');
+ const color = colors
+ .map((c, i) => {
+ const percentFirst = Math.floor(spacing * i);
+ const percentSecond = Math.floor(spacing * (i + 1));
+ return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
+ })
+ .join(', ');
return `linear-gradient(${color})`;
}
@@ -97,17 +99,16 @@ export default class DropdownUtils {
data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap));
- Object.keys(dataMap)
- .forEach((key) => {
- const label = dataMap[key];
+ Object.keys(dataMap).forEach(key => {
+ const label = dataMap[key];
- if (label.multipleColors) {
- label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
- label.text_color = '#000000';
- }
+ if (label.multipleColors) {
+ label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
+ label.text_color = '#000000';
+ }
- results.push(label);
- });
+ results.push(label);
+ });
results.preprocessed = true;
@@ -118,8 +119,7 @@ export default class DropdownUtils {
const { input, allowedKeys } = config;
const updatedItem = item;
const searchInput = DropdownUtils.getSearchQuery(input);
- const { lastToken, tokens } =
- FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
+ const { lastToken, tokens } = FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
@@ -154,7 +154,10 @@ export default class DropdownUtils {
static getVisualTokenValues(visualToken) {
const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim();
- let tokenValue = visualToken && visualToken.querySelector('.value') && visualToken.querySelector('.value').textContent.trim();
+ let tokenValue =
+ visualToken &&
+ visualToken.querySelector('.value') &&
+ visualToken.querySelector('.value').textContent.trim();
if (tokenName === 'label' && tokenValue) {
// remove leading symbol and wrapping quotes
tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, '');
@@ -174,7 +177,7 @@ export default class DropdownUtils {
tokens.splice(inputIndex + 1);
}
- tokens.forEach((token) => {
+ tokens.forEach(token => {
if (token.classList.contains('js-visual-token')) {
const name = token.querySelector('.name');
const value = token.querySelector('.value');
@@ -194,8 +197,9 @@ export default class DropdownUtils {
values.push(name.innerText);
}
} else if (token.classList.contains('input-token')) {
- const { isLastVisualTokenValid } =
- FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ const {
+ isLastVisualTokenValid,
+ } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value;
@@ -209,9 +213,7 @@ export default class DropdownUtils {
}
});
- return values
- .map(value => value.trim())
- .join(' ');
+ return values.map(value => value.trim()).join(' ');
}
static getSearchInput(filteredSearchInput) {
@@ -227,7 +229,9 @@ export default class DropdownUtils {
// Replace all spaces inside quote marks with underscores
// (will continue to match entire string until an end quote is found if any)
// This helps with matching the beginning & end of a token:key
- inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
+ inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str =>
+ str.replace(/\s/g, '_'),
+ );
// Get the right position for the word selected
// Regex matches first space
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
index fb4ae1d17dd..4eb67ff7649 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
@@ -87,10 +87,12 @@ export default class FilteredSearchDropdown {
dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
- this.input.dispatchEvent(new CustomEvent('input', {
- bubbles: true,
- cancelable: true,
- }));
+ this.input.dispatchEvent(
+ new CustomEvent('input', {
+ bubbles: true,
+ cancelable: true,
+ }),
+ );
}
dispatchFormSubmitEvent() {
@@ -114,7 +116,7 @@ export default class FilteredSearchDropdown {
if (!data) return;
- const results = data.map((o) => {
+ const results = data.map(o => {
const updated = o;
updated.droplab_hidden = false;
return updated;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index a09ad3e4758..e01dedbb57c 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
@@ -42,19 +42,21 @@ export default class FilteredSearchTokenKeys {
}
searchByKeyParam(keyParam) {
- return this.tokenKeysWithAlternative.find((tokenKey) => {
- let tokenKeyParam = tokenKey.key;
+ return (
+ this.tokenKeysWithAlternative.find(tokenKey => {
+ let tokenKeyParam = tokenKey.key;
- // Replace hyphen with underscore to compare keyParam with tokenKeyParam
- // e.g. 'my-reaction' => 'my_reaction'
- tokenKeyParam = tokenKeyParam.replace('-', '_');
+ // Replace hyphen with underscore to compare keyParam with tokenKeyParam
+ // e.g. 'my-reaction' => 'my_reaction'
+ tokenKeyParam = tokenKeyParam.replace('-', '_');
- if (tokenKey.param) {
- tokenKeyParam += `_${tokenKey.param}`;
- }
+ if (tokenKey.param) {
+ tokenKeyParam += `_${tokenKey.param}`;
+ }
- return keyParam === tokenKeyParam;
- }) || null;
+ return keyParam === tokenKeyParam;
+ }) || null
+ );
}
searchByConditionUrl(url) {
@@ -62,8 +64,10 @@ export default class FilteredSearchTokenKeys {
}
searchByConditionKeyValue(key, value) {
- return this.conditions
- .find(condition => condition.tokenKey === key && condition.value === value) || null;
+ return (
+ this.conditions.find(condition => condition.tokenKey === key && condition.value === value) ||
+ null
+ );
}
addExtraTokensForMergeRequests() {
diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
index d75610f6d68..b5c4cb15aac 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
@@ -4,41 +4,48 @@ export default class FilteredSearchTokenizer {
static processTokens(input, allowedKeys) {
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
- const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
+ const tokenRegex = new RegExp(
+ `(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`,
+ 'g',
+ );
const tokens = [];
const tokenIndexes = []; // stores key+value for simple search
let lastToken = null;
- const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
- let tokenValue = v1 || v2 || v3;
- let tokenSymbol = symbol;
- let tokenIndex = '';
-
- if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
- tokenSymbol = tokenValue;
- tokenValue = '';
- }
-
- tokenIndex = `${key}:${tokenValue}`;
-
- // Prevent adding duplicates
- if (tokenIndexes.indexOf(tokenIndex) === -1) {
- tokenIndexes.push(tokenIndex);
-
- tokens.push({
- key,
- value: tokenValue || '',
- symbol: tokenSymbol || '',
- });
- }
-
- return '';
- }).replace(/\s{2,}/g, ' ').trim() || '';
+ const searchToken =
+ input
+ .replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
+ let tokenValue = v1 || v2 || v3;
+ let tokenSymbol = symbol;
+ let tokenIndex = '';
+
+ if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
+ tokenSymbol = tokenValue;
+ tokenValue = '';
+ }
+
+ tokenIndex = `${key}:${tokenValue}`;
+
+ // Prevent adding duplicates
+ if (tokenIndexes.indexOf(tokenIndex) === -1) {
+ tokenIndexes.push(tokenIndex);
+
+ tokens.push({
+ key,
+ value: tokenValue || '',
+ symbol: tokenSymbol || '',
+ });
+ }
+
+ return '';
+ })
+ .replace(/\s{2,}/g, ' ')
+ .trim() || '';
if (tokens.length > 0) {
const last = tokens[tokens.length - 1];
const lastString = `${last.key}:${last.symbol}${last.value}`;
- lastToken = input.lastIndexOf(lastString) ===
- input.length - lastString.length ? last : searchToken;
+ lastToken =
+ input.lastIndexOf(lastString) === input.length - lastString.length ? last : searchToken;
} else {
lastToken = searchToken;
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index 0854c1822fb..c23d4c484a5 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -13,7 +13,10 @@ export default class FilteredSearchVisualTokens {
return {
lastVisualToken,
- isLastVisualTokenValid: lastVisualToken === null || lastVisualToken.className.indexOf('filtered-search-term') !== -1 || (lastVisualToken && lastVisualToken.querySelector('.value') !== null),
+ isLastVisualTokenValid:
+ lastVisualToken === null ||
+ lastVisualToken.className.indexOf('filtered-search-term') !== -1 ||
+ (lastVisualToken && lastVisualToken.querySelector('.value') !== null),
};
}
@@ -33,7 +36,9 @@ export default class FilteredSearchVisualTokens {
}
static unselectTokens() {
- const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected');
+ const otherTokens = FilteredSearchContainer.container.querySelectorAll(
+ '.js-visual-token .selectable.selected',
+ );
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
}
@@ -56,11 +61,7 @@ export default class FilteredSearchVisualTokens {
}
static createVisualTokenElementHTML(options = {}) {
- const {
- canEdit = true,
- uppercaseTokenName = false,
- capitalizeTokenValue = false,
- } = options;
+ const { canEdit = true, uppercaseTokenName = false, capitalizeTokenValue = false } = options;
return `
<div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
@@ -115,15 +116,20 @@ export default class FilteredSearchVisualTokens {
return AjaxCache.retrieve(labelsEndpoint)
.then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint))
- .then((labels) => {
- const matchingLabel = (labels || []).find(label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue);
+ .then(labels => {
+ const matchingLabel = (labels || []).find(
+ label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue,
+ );
if (!matchingLabel) {
return;
}
- FilteredSearchVisualTokens
- .setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color);
+ FilteredSearchVisualTokens.setTokenStyle(
+ tokenValueContainer,
+ matchingLabel.color,
+ matchingLabel.text_color,
+ );
})
.catch(() => new Flash('An error occurred while fetching label colors.'));
}
@@ -134,39 +140,43 @@ export default class FilteredSearchVisualTokens {
}
const username = tokenValue.replace(/^@/, '');
- return UsersCache.retrieve(username)
- .then((user) => {
- if (!user) {
- return;
- }
-
- /* eslint-disable no-param-reassign */
- tokenValueContainer.dataset.originalValue = tokenValue;
- tokenValueElement.innerHTML = `
+ return (
+ UsersCache.retrieve(username)
+ .then(user => {
+ if (!user) {
+ return;
+ }
+
+ /* eslint-disable no-param-reassign */
+ tokenValueContainer.dataset.originalValue = tokenValue;
+ tokenValueElement.innerHTML = `
<img class="avatar s20" src="${user.avatar_url}" alt="">
${_.escape(user.name)}
`;
- /* eslint-enable no-param-reassign */
- })
- // ignore error and leave username in the search bar
- .catch(() => { });
+ /* eslint-enable no-param-reassign */
+ })
+ // ignore error and leave username in the search bar
+ .catch(() => {})
+ );
}
static updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) {
const container = tokenValueContainer;
const element = tokenValueElement;
- return import(/* webpackChunkName: 'emoji' */ '../emoji')
- .then((Emoji) => {
- if (!Emoji.isEmojiNameValid(tokenValue)) {
- return;
- }
-
- container.dataset.originalValue = tokenValue;
- element.innerHTML = Emoji.glEmojiTag(tokenValue);
- })
- // ignore error and leave emoji name in the search bar
- .catch(() => { });
+ return (
+ import(/* webpackChunkName: 'emoji' */ '../emoji')
+ .then(Emoji => {
+ if (!Emoji.isEmojiNameValid(tokenValue)) {
+ return;
+ }
+
+ container.dataset.originalValue = tokenValue;
+ element.innerHTML = Emoji.glEmojiTag(tokenValue);
+ })
+ // ignore error and leave emoji name in the search bar
+ .catch(() => {})
+ );
}
static renderVisualTokenValue(parentElement, tokenName, tokenValue) {
@@ -177,24 +187,23 @@ export default class FilteredSearchVisualTokens {
const tokenType = tokenName.toLowerCase();
if (tokenType === 'label') {
FilteredSearchVisualTokens.updateLabelTokenColor(tokenValueContainer, tokenValue);
- } else if ((tokenType === 'author') || (tokenType === 'assignee')) {
+ } else if (tokenType === 'author' || tokenType === 'assignee') {
FilteredSearchVisualTokens.updateUserTokenAppearance(
- tokenValueContainer, tokenValueElement, tokenValue,
+ tokenValueContainer,
+ tokenValueElement,
+ tokenValue,
);
} else if (tokenType === 'my-reaction') {
FilteredSearchVisualTokens.updateEmojiTokenAppearance(
- tokenValueContainer, tokenValueElement, tokenValue,
+ tokenValueContainer,
+ tokenValueElement,
+ tokenValue,
);
}
}
static addVisualTokenElement(name, value, options = {}) {
- const {
- isSearchTerm = false,
- canEdit,
- uppercaseTokenName,
- capitalizeTokenValue,
- } = options;
+ const { isSearchTerm = false, canEdit, uppercaseTokenName, capitalizeTokenValue } = options;
const li = document.createElement('li');
li.classList.add('js-visual-token');
li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
@@ -217,8 +226,10 @@ export default class FilteredSearchVisualTokens {
}
static addValueToPreviousVisualTokenElement(value) {
- const { lastVisualToken, isLastVisualTokenValid } =
- FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ const {
+ lastVisualToken,
+ isLastVisualTokenValid,
+ } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (!isLastVisualTokenValid && lastVisualToken.classList.contains('filtered-search-token')) {
const name = FilteredSearchVisualTokens.getLastTokenPartial();
@@ -228,13 +239,15 @@ export default class FilteredSearchVisualTokens {
}
}
- static addFilterVisualToken(tokenName, tokenValue, {
- canEdit,
- uppercaseTokenName = false,
- capitalizeTokenValue = false,
- } = {}) {
- const { lastVisualToken, isLastVisualTokenValid }
- = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ static addFilterVisualToken(
+ tokenName,
+ tokenValue,
+ { canEdit, uppercaseTokenName = false, capitalizeTokenValue = false } = {},
+ ) {
+ const {
+ lastVisualToken,
+ isLastVisualTokenValid,
+ } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const { addVisualTokenElement } = FilteredSearchVisualTokens;
if (isLastVisualTokenValid) {
@@ -308,8 +321,7 @@ export default class FilteredSearchVisualTokens {
static tokenizeInput() {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
- const { isLastVisualTokenValid } =
- FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (input.value) {
if (isLastVisualTokenValid) {
@@ -375,8 +387,7 @@ export default class FilteredSearchVisualTokens {
FilteredSearchVisualTokens.tokenizeInput();
if (!tokenContainer.lastElementChild.isEqualNode(inputLi)) {
- const { isLastVisualTokenValid } =
- FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
+ const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (!isLastVisualTokenValid) {
const lastPartial = FilteredSearchVisualTokens.getLastTokenPartial();
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
index cc7291c9f59..b70125c80ca 100644
--- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
+++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js
@@ -1,34 +1,39 @@
import FilteredSearchTokenKeys from './filtered_search_token_keys';
-export const tokenKeys = [{
- key: 'author',
- type: 'string',
- param: 'username',
- symbol: '@',
- icon: 'pencil',
- tag: '@author',
-}, {
- key: 'assignee',
- type: 'string',
- param: 'username',
- symbol: '@',
- icon: 'user',
- tag: '@assignee',
-}, {
- key: 'milestone',
- type: 'string',
- param: 'title',
- symbol: '%',
- icon: 'clock',
- tag: '%milestone',
-}, {
- key: 'label',
- type: 'array',
- param: 'name[]',
- symbol: '~',
- icon: 'labels',
- tag: '~label',
-}];
+export const tokenKeys = [
+ {
+ key: 'author',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+ icon: 'pencil',
+ tag: '@author',
+ },
+ {
+ key: 'assignee',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+ icon: 'user',
+ tag: '@assignee',
+ },
+ {
+ key: 'milestone',
+ type: 'string',
+ param: 'title',
+ symbol: '%',
+ icon: 'clock',
+ tag: '%milestone',
+ },
+ {
+ key: 'label',
+ type: 'array',
+ param: 'name[]',
+ symbol: '~',
+ icon: 'labels',
+ tag: '~label',
+ },
+];
if (gon.current_user_id) {
// Appending tokenkeys only logged-in
@@ -42,36 +47,52 @@ if (gon.current_user_id) {
});
}
-export const alternativeTokenKeys = [{
- key: 'label',
- type: 'string',
- param: 'name',
- symbol: '~',
-}];
+export const alternativeTokenKeys = [
+ {
+ key: 'label',
+ type: 'string',
+ param: 'name',
+ symbol: '~',
+ },
+];
-export const conditions = [{
- url: 'assignee_id=0',
- tokenKey: 'assignee',
- value: 'none',
-}, {
- url: 'milestone_title=No+Milestone',
- tokenKey: 'milestone',
- value: 'none',
-}, {
- url: 'milestone_title=%23upcoming',
- tokenKey: 'milestone',
- value: 'upcoming',
-}, {
- url: 'milestone_title=%23started',
- tokenKey: 'milestone',
- value: 'started',
-}, {
- url: 'label_name[]=No+Label',
- tokenKey: 'label',
- value: 'none',
-}];
+export const conditions = [
+ {
+ url: 'assignee_id=0',
+ tokenKey: 'assignee',
+ value: 'none',
+ },
+ {
+ url: 'milestone_title=No+Milestone',
+ tokenKey: 'milestone',
+ value: 'none',
+ },
+ {
+ url: 'milestone_title=Any+Milestone',
+ tokenKey: 'milestone',
+ value: 'any',
+ },
+ {
+ url: 'milestone_title=%23upcoming',
+ tokenKey: 'milestone',
+ value: 'upcoming',
+ },
+ {
+ url: 'milestone_title=%23started',
+ tokenKey: 'milestone',
+ value: 'started',
+ },
+ {
+ url: 'label_name[]=No+Label',
+ tokenKey: 'label',
+ value: 'none',
+ },
+];
-const IssuableFilteredSearchTokenKeys =
- new FilteredSearchTokenKeys(tokenKeys, alternativeTokenKeys, conditions);
+const IssuableFilteredSearchTokenKeys = new FilteredSearchTokenKeys(
+ tokenKeys,
+ alternativeTokenKeys,
+ conditions,
+);
export default IssuableFilteredSearchTokenKeys;
diff --git a/app/assets/javascripts/filtered_search/recent_searches_root.js b/app/assets/javascripts/filtered_search/recent_searches_root.js
index c1efa9c86f4..6c8e77a7fe5 100644
--- a/app/assets/javascripts/filtered_search/recent_searches_root.js
+++ b/app/assets/javascripts/filtered_search/recent_searches_root.js
@@ -3,11 +3,7 @@ import RecentSearchesDropdownContent from './components/recent_searches_dropdown
import eventHub from './event_hub';
class RecentSearchesRoot {
- constructor(
- recentSearchesStore,
- recentSearchesService,
- wrapperElement,
- ) {
+ constructor(recentSearchesStore, recentSearchesService, wrapperElement) {
this.store = recentSearchesStore;
this.service = recentSearchesService;
this.wrapperElement = wrapperElement;
@@ -35,7 +31,9 @@ class RecentSearchesRoot {
components: {
RecentSearchesDropdownContent,
},
- data() { return state; },
+ data() {
+ return state;
+ },
template: `
<recent-searches-dropdown-content
:items="recentSearches"
@@ -57,7 +55,6 @@ class RecentSearchesRoot {
this.vm.$destroy();
}
}
-
}
export default RecentSearchesRoot;
diff --git a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
index aaa0c349d93..76d40bfdaf8 100644
--- a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
+++ b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js
@@ -2,11 +2,14 @@ import _ from 'underscore';
class RecentSearchesStore {
constructor(initialState = {}, allowedKeys) {
- this.state = Object.assign({
- isLocalStorageAvailable: true,
- recentSearches: [],
- allowedKeys,
- }, initialState);
+ this.state = Object.assign(
+ {
+ isLocalStorageAvailable: true,
+ recentSearches: [],
+ allowedKeys,
+ },
+ initialState,
+ );
}
addRecentSearch(newSearch) {
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index a29de9ae899..749c09f897c 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -8,14 +8,19 @@ const hideFlash = (flashEl, fadeTransition = true) => {
});
}
- flashEl.addEventListener('transitionend', () => {
- flashEl.remove();
- window.dispatchEvent(new Event('resize'));
- if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
- }, {
- once: true,
- passive: true,
- });
+ flashEl.addEventListener(
+ 'transitionend',
+ () => {
+ flashEl.remove();
+ window.dispatchEvent(new Event('resize'));
+ if (document.body.classList.contains('flash-shown'))
+ document.body.classList.remove('flash-shown');
+ },
+ {
+ once: true,
+ passive: true,
+ },
+ );
if (!fadeTransition) flashEl.dispatchEvent(new Event('transitionend'));
};
@@ -30,12 +35,12 @@ const createAction = config => `
</a>
`;
-const createFlashEl = (message, type, isInContentWrapper = false) => `
+const createFlashEl = (message, type, isFixedLayout = false) => `
<div
class="flash-${type}"
>
<div
- class="flash-text ${isInContentWrapper ? 'container-fluid container-limited' : ''}"
+ class="flash-text ${isFixedLayout ? 'container-fluid container-limited limit-container-width' : ''}"
>
${_.escape(message)}
</div>
@@ -69,12 +74,13 @@ const createFlash = function createFlash(
addBodyClass = false,
) {
const flashContainer = parent.querySelector('.flash-container');
+ const navigation = parent.querySelector('.content');
if (!flashContainer) return null;
- const isInContentWrapper = flashContainer.parentNode.classList.contains('content-wrapper');
+ const isFixedLayout = navigation ? navigation.parentNode.classList.contains('container-limited') : true;
- flashContainer.innerHTML = createFlashEl(message, type, isInContentWrapper);
+ flashContainer.innerHTML = createFlashEl(message, type, isFixedLayout);
const flashEl = flashContainer.querySelector(`.flash-${type}`);
removeFlashClickListener(flashEl, fadeTransition);
@@ -83,7 +89,9 @@ const createFlash = function createFlash(
flashEl.innerHTML += createAction(actionConfig);
if (actionConfig.clickHandler) {
- flashEl.querySelector('.flash-action').addEventListener('click', e => actionConfig.clickHandler(e));
+ flashEl
+ .querySelector('.flash-action')
+ .addEventListener('click', e => actionConfig.clickHandler(e));
}
}
@@ -94,11 +102,5 @@ const createFlash = function createFlash(
return flashContainer;
};
-export {
- createFlash as default,
- createFlashEl,
- createAction,
- hideFlash,
- removeFlashClickListener,
-};
+export { createFlash as default, createFlashEl, createAction, hideFlash, removeFlashClickListener };
window.Flash = createFlash;
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index f820f0dc3f0..3ac00c51df4 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -11,9 +11,13 @@ let sidebar;
export const mousePos = [];
-export const setSidebar = (el) => { sidebar = el; };
+export const setSidebar = el => {
+ sidebar = el;
+};
export const getOpenMenu = () => currentOpenMenu;
-export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; };
+export const setOpenMenu = (menu = null) => {
+ currentOpenMenu = menu;
+};
export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
@@ -21,9 +25,10 @@ let headerHeight = 50;
export const getHeaderHeight = () => headerHeight;
-export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
+export const isSidebarCollapsed = () =>
+ sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
-export const canShowActiveSubItems = (el) => {
+export const canShowActiveSubItems = el => {
if (el.classList.contains('active') && !isSidebarCollapsed()) {
return false;
}
@@ -31,7 +36,10 @@ export const canShowActiveSubItems = (el) => {
return true;
};
-export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg';
+export const canShowSubItems = () =>
+ bp.getBreakpointSize() === 'sm' ||
+ bp.getBreakpointSize() === 'md' ||
+ bp.getBreakpointSize() === 'lg';
export const getHideSubItemsInterval = () => {
if (!currentOpenMenu || !mousePos.length) return 0;
@@ -41,11 +49,12 @@ export const getHideSubItemsInterval = () => {
const currentMousePosY = currentMousePos.y;
const [menuTop, menuBottom] = menuCornerLocs;
- if (currentMousePosY < menuTop.y ||
- currentMousePosY > menuBottom.y) return 0;
+ if (currentMousePosY < menuTop.y || currentMousePosY > menuBottom.y) return 0;
- if (slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) &&
- slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop)) {
+ if (
+ slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) &&
+ slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop)
+ ) {
return HIDE_INTERVAL_TIMEOUT;
}
@@ -56,11 +65,12 @@ export const calculateTop = (boundingRect, outerHeight) => {
const windowHeight = window.innerHeight;
const bottomOverflow = windowHeight - (boundingRect.top + outerHeight);
- return bottomOverflow < 0 ? (boundingRect.top - outerHeight) + boundingRect.height :
- boundingRect.top;
+ return bottomOverflow < 0
+ ? boundingRect.top - outerHeight + boundingRect.height
+ : boundingRect.top;
};
-export const hideMenu = (el) => {
+export const hideMenu = el => {
if (!el) return;
const parentEl = el.parentNode;
@@ -101,7 +111,7 @@ export const moveSubItemsToPosition = (el, subItems) => {
}
};
-export const showSubLevelItems = (el) => {
+export const showSubLevelItems = el => {
const subItems = el.querySelector('.sidebar-sub-level-items');
const isIconOnly = subItems && subItems.classList.contains('is-fly-out-only');
@@ -128,16 +138,20 @@ export const mouseEnterTopItems = (el, timeout = getHideSubItemsInterval()) => {
}, timeout);
};
-export const mouseLeaveTopItem = (el) => {
+export const mouseLeaveTopItem = el => {
const subItems = el.querySelector('.sidebar-sub-level-items');
- if (!canShowSubItems() || !canShowActiveSubItems(el) ||
- (subItems && subItems === currentOpenMenu)) return;
+ if (
+ !canShowSubItems() ||
+ !canShowActiveSubItems(el) ||
+ (subItems && subItems === currentOpenMenu)
+ )
+ return;
el.classList.remove(IS_OVER_CLASS);
};
-export const documentMouseMove = (e) => {
+export const documentMouseMove = e => {
mousePos.push({
x: e.clientX,
y: e.clientY,
@@ -146,7 +160,7 @@ export const documentMouseMove = (e) => {
if (mousePos.length > 6) mousePos.shift();
};
-export const subItemsMouseLeave = (relatedTarget) => {
+export const subItemsMouseLeave = relatedTarget => {
clearTimeout(timeoutId);
if (relatedTarget && !relatedTarget.closest(`.${IS_OVER_CLASS}`)) {
@@ -174,7 +188,7 @@ export default () => {
headerHeight = document.querySelector('.nav-sidebar').offsetTop;
- items.forEach((el) => {
+ items.forEach(el => {
const subItems = el.querySelector('.sidebar-sub-level-items');
if (subItems) {
diff --git a/app/assets/javascripts/gl_field_error.js b/app/assets/javascripts/gl_field_error.js
index 87c6e37b9fb..a5b8c357e8a 100644
--- a/app/assets/javascripts/gl_field_error.js
+++ b/app/assets/javascripts/gl_field_error.js
@@ -116,7 +116,8 @@ export default class GlFieldError {
this.form.focusOnFirstInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup
- this.inputElement.off('keyup.fieldValidator')
+ this.inputElement
+ .off('keyup.fieldValidator')
.on('keyup.fieldValidator', this.updateValidity.bind(this));
}
diff --git a/app/assets/javascripts/gl_field_errors.js b/app/assets/javascripts/gl_field_errors.js
index b9c51045b1d..3764e7ab422 100644
--- a/app/assets/javascripts/gl_field_errors.js
+++ b/app/assets/javascripts/gl_field_errors.js
@@ -16,9 +16,12 @@ export default class GlFieldErrors {
initValidators() {
// register selectors here as needed
const validateSelectors = [':text', ':password', '[type=email]']
- .map(selector => `input${selector}`).join(',');
+ .map(selector => `input${selector}`)
+ .join(',');
- this.state.inputs = this.form.find(validateSelectors).toArray()
+ this.state.inputs = this.form
+ .find(validateSelectors)
+ .toArray()
.filter(input => !input.classList.contains(customValidationFlag))
.map(input => new GlFieldError({ input, formErrors: this }));
@@ -42,7 +45,7 @@ export default class GlFieldErrors {
/* Public method for triggering validity updates manually */
updateFormValidityState() {
- this.state.inputs.forEach((field) => {
+ this.state.inputs.forEach(field => {
if (field.state.submitted) {
field.updateValidity();
}
@@ -50,8 +53,9 @@ export default class GlFieldErrors {
}
focusOnFirstInvalid() {
- const firstInvalid = this.state.inputs
- .filter(input => !input.inputDomElement.validity.valid)[0];
+ const firstInvalid = this.state.inputs.filter(
+ input => !input.inputDomElement.validity.valid,
+ )[0];
firstInvalid.inputElement.focus();
}
}
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index e672284a2d0..f842d2d74db 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -39,7 +39,10 @@ export default class GLForm {
this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
- gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
+ gl.utils.disableButtonIfEmptyField(
+ this.form.find('.js-note-text'),
+ this.form.find('.js-comment-button, .js-note-new-discussion'),
+ );
this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
this.autoComplete.setup(this.form.find('.js-gfm-input'), this.enableGFM);
dropzoneInput(this.form);
@@ -55,11 +58,9 @@ export default class GLForm {
}
setupAutosize() {
- this.textarea.off('autosize:resized')
- .on('autosize:resized', this.setHeightData.bind(this));
+ this.textarea.off('autosize:resized').on('autosize:resized', this.setHeightData.bind(this));
- this.textarea.off('mouseup.autosize')
- .on('mouseup.autosize', this.destroyAutosize.bind(this));
+ this.textarea.off('mouseup.autosize').on('mouseup.autosize', this.destroyAutosize.bind(this));
setTimeout(() => {
autosize(this.textarea);
@@ -91,10 +92,14 @@ export default class GLForm {
addEventListeners() {
this.textarea.on('focus', function focusTextArea() {
- $(this).closest('.md-area').addClass('is-focused');
+ $(this)
+ .closest('.md-area')
+ .addClass('is-focused');
});
this.textarea.on('blur', function blurTextArea() {
- $(this).closest('.md-area').removeClass('is-focused');
+ $(this)
+ .closest('.md-area')
+ .removeClass('is-focused');
});
}
}
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
index beaac61e887..dcda625f587 100644
--- a/app/assets/javascripts/group_avatar.js
+++ b/app/assets/javascripts/group_avatar.js
@@ -7,8 +7,9 @@ export default function groupAvatar() {
});
$('.js-group-avatar-input').on('change', function onChangeAvatarInput() {
const form = $(this).closest('form');
- // eslint-disable-next-line no-useless-escape
- const filename = $(this).val().replace(/^.*[\\\/]/, '');
+ const filename = $(this)
+ .val()
+ .replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape
return form.find('.js-avatar-filename').text(filename);
});
}
diff --git a/app/assets/javascripts/group_label_subscription.js b/app/assets/javascripts/group_label_subscription.js
index d33e3a37580..9b74560f914 100644
--- a/app/assets/javascripts/group_label_subscription.js
+++ b/app/assets/javascripts/group_label_subscription.js
@@ -23,7 +23,8 @@ export default class GroupLabelSubscription {
event.preventDefault();
const url = this.$unsubscribeButtons.attr('data-url');
- axios.post(url)
+ axios
+ .post(url)
.then(() => {
this.toggleSubscriptionButtons();
this.$unsubscribeButtons.removeAttr('data-url');
@@ -39,7 +40,8 @@ export default class GroupLabelSubscription {
this.$unsubscribeButtons.attr('data-url', url);
- axios.post(url)
+ axios
+ .post(url)
.then(() => GroupLabelSubscription.setNewTooltip($btn))
.then(() => this.toggleSubscriptionButtons())
.catch(() => flash(__('There was an error when subscribing to this label.')));
@@ -58,6 +60,8 @@ export default class GroupLabelSubscription {
const newTitle = tooltipTitles[type];
$('.js-unsubscribe-button', $button.closest('.label-actions-list'))
- .tooltip('hide').attr('title', newTitle).tooltip('_fixTitle');
+ .tooltip('hide')
+ .attr('title', newTitle)
+ .tooltip('_fixTitle');
}
}
diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue
index 87ab5480c15..829924ba63c 100644
--- a/app/assets/javascripts/groups/components/item_stats.vue
+++ b/app/assets/javascripts/groups/components/item_stats.vue
@@ -1,44 +1,44 @@
<script>
- import icon from '~/vue_shared/components/icon.vue';
- import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
- import {
- ITEM_TYPE,
- VISIBILITY_TYPE_ICON,
- GROUP_VISIBILITY_TYPE,
- PROJECT_VISIBILITY_TYPE,
- } from '../constants';
- import itemStatsValue from './item_stats_value.vue';
+import icon from '~/vue_shared/components/icon.vue';
+import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import {
+ ITEM_TYPE,
+ VISIBILITY_TYPE_ICON,
+ GROUP_VISIBILITY_TYPE,
+ PROJECT_VISIBILITY_TYPE,
+} from '../constants';
+import itemStatsValue from './item_stats_value.vue';
- export default {
- components: {
- icon,
- timeAgoTooltip,
- itemStatsValue,
+export default {
+ components: {
+ icon,
+ timeAgoTooltip,
+ itemStatsValue,
+ },
+ props: {
+ item: {
+ type: Object,
+ required: true,
},
- props: {
- item: {
- type: Object,
- required: true,
- },
+ },
+ computed: {
+ visibilityIcon() {
+ return VISIBILITY_TYPE_ICON[this.item.visibility];
},
- computed: {
- visibilityIcon() {
- return VISIBILITY_TYPE_ICON[this.item.visibility];
- },
- visibilityTooltip() {
- if (this.item.type === ITEM_TYPE.GROUP) {
- return GROUP_VISIBILITY_TYPE[this.item.visibility];
- }
- return PROJECT_VISIBILITY_TYPE[this.item.visibility];
- },
- isProject() {
- return this.item.type === ITEM_TYPE.PROJECT;
- },
- isGroup() {
- return this.item.type === ITEM_TYPE.GROUP;
- },
+ visibilityTooltip() {
+ if (this.item.type === ITEM_TYPE.GROUP) {
+ return GROUP_VISIBILITY_TYPE[this.item.visibility];
+ }
+ return PROJECT_VISIBILITY_TYPE[this.item.visibility];
},
- };
+ isProject() {
+ return this.item.type === ITEM_TYPE.PROJECT;
+ },
+ isGroup() {
+ return this.item.type === ITEM_TYPE.GROUP;
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/groups/components/item_stats_value.vue b/app/assets/javascripts/groups/components/item_stats_value.vue
index ef9f2bca76c..c542ca946d3 100644
--- a/app/assets/javascripts/groups/components/item_stats_value.vue
+++ b/app/assets/javascripts/groups/components/item_stats_value.vue
@@ -1,52 +1,52 @@
<script>
- import tooltip from '~/vue_shared/directives/tooltip';
- import icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import icon from '~/vue_shared/components/icon.vue';
- export default {
- components: {
- icon,
+export default {
+ components: {
+ icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ title: {
+ type: String,
+ required: false,
+ default: '',
},
- directives: {
- tooltip,
+ cssClass: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- title: {
- type: String,
- required: false,
- default: '',
- },
- cssClass: {
- type: String,
- required: false,
- default: '',
- },
- iconName: {
- type: String,
- required: true,
- },
- tooltipPlacement: {
- type: String,
- required: false,
- default: 'bottom',
- },
- /**
- * value could either be number or string
- * as `memberCount` is always passed as string
- * while `subgroupCount` & `projectCount`
- * are always number
- */
- value: {
- type: [Number, String],
- required: false,
- default: '',
- },
+ iconName: {
+ type: String,
+ required: true,
},
- computed: {
- isValuePresent() {
- return this.value !== '';
- },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'bottom',
},
- };
+ /**
+ * value could either be number or string
+ * as `memberCount` is always passed as string
+ * while `subgroupCount` & `projectCount`
+ * are always number
+ */
+ value: {
+ type: [Number, String],
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ isValuePresent() {
+ return this.value !== '';
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/groups/new_group_child.js b/app/assets/javascripts/groups/new_group_child.js
index a120d501e35..012177479c6 100644
--- a/app/assets/javascripts/groups/new_group_child.js
+++ b/app/assets/javascripts/groups/new_group_child.js
@@ -37,20 +37,22 @@ export default class NewGroupChild {
getDroplabConfig() {
return {
- InputSetter: [{
- input: this.newGroupChildButton,
- valueAttribute: 'data-value',
- inputAttribute: 'data-action',
- }, {
- input: this.newGroupChildButton,
- valueAttribute: 'data-text',
- }],
+ InputSetter: [
+ {
+ input: this.newGroupChildButton,
+ valueAttribute: 'data-value',
+ inputAttribute: 'data-action',
+ },
+ {
+ input: this.newGroupChildButton,
+ valueAttribute: 'data-text',
+ },
+ ],
};
}
bindEvents() {
- this.newGroupChildButton
- .addEventListener('click', this.onClickNewGroupChildButton.bind(this));
+ this.newGroupChildButton.addEventListener('click', this.onClickNewGroupChildButton.bind(this));
}
onClickNewGroupChildButton(e) {
diff --git a/app/assets/javascripts/groups/store/groups_store.js b/app/assets/javascripts/groups/store/groups_store.js
index 4a7569078a1..16f95d5a0cc 100644
--- a/app/assets/javascripts/groups/store/groups_store.js
+++ b/app/assets/javascripts/groups/store/groups_store.js
@@ -17,13 +17,14 @@ export default class GroupsStore {
}
setSearchedGroups(rawGroups) {
- const formatGroups = groups => groups.map((group) => {
- const formattedGroup = this.formatGroupItem(group);
- if (formattedGroup.children && formattedGroup.children.length) {
- formattedGroup.children = formatGroups(formattedGroup.children);
- }
- return formattedGroup;
- });
+ const formatGroups = groups =>
+ groups.map(group => {
+ const formattedGroup = this.formatGroupItem(group);
+ if (formattedGroup.children && formattedGroup.children.length) {
+ formattedGroup.children = formatGroups(formattedGroup.children);
+ }
+ return formattedGroup;
+ });
if (rawGroups && rawGroups.length) {
this.state.groups = formatGroups(rawGroups);
@@ -62,10 +63,10 @@ export default class GroupsStore {
formatGroupItem(rawGroupItem) {
const groupChildren = rawGroupItem.children || [];
- const groupIsOpen = (groupChildren.length > 0) || false;
- const childrenCount = this.hideProjects ?
- rawGroupItem.subgroup_count :
- rawGroupItem.children_count;
+ const groupIsOpen = groupChildren.length > 0 || false;
+ const childrenCount = this.hideProjects
+ ? rawGroupItem.subgroup_count
+ : rawGroupItem.children_count;
return {
id: rawGroupItem.id,
diff --git a/app/assets/javascripts/groups/transfer_dropdown.js b/app/assets/javascripts/groups/transfer_dropdown.js
index e0eb118ddf7..26510fcdb2a 100644
--- a/app/assets/javascripts/groups/transfer_dropdown.js
+++ b/app/assets/javascripts/groups/transfer_dropdown.js
@@ -22,7 +22,7 @@ export default class TransferDropdown {
search: { fields: ['text'] },
data: extraOptions.concat(this.data),
text: item => item.text,
- clicked: (options) => {
+ clicked: options => {
const { e } = options;
e.preventDefault();
this.assignSelected(options.selectedObj);
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index e37fc5c4be6..b4a3037c1b7 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -23,7 +23,7 @@ export default function groupsSelect() {
axios[params.type.toLowerCase()](params.url, {
params: params.data,
})
- .then((res) => {
+ .then(res => {
const results = res.data || [];
const headers = normalizeHeaders(res.headers);
const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
@@ -36,7 +36,8 @@ export default function groupsSelect() {
more,
},
});
- }).catch(params.error);
+ })
+ .catch(params.error);
},
data(search, page) {
return {
@@ -68,7 +69,9 @@ export default function groupsSelect() {
}
},
formatResult(object) {
- return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`;
+ return `<div class='group-result'> <div class='group-name'>${
+ object.full_name
+ }</div> <div class='group-path'>${object.full_path}</div> </div>`;
},
formatSelection(object) {
return object.full_name;
diff --git a/app/assets/javascripts/helpers/avatar_helper.js b/app/assets/javascripts/helpers/avatar_helper.js
index d3b1d0f11fd..35ac7b2629c 100644
--- a/app/assets/javascripts/helpers/avatar_helper.js
+++ b/app/assets/javascripts/helpers/avatar_helper.js
@@ -19,7 +19,9 @@ export function renderIdenticon(entity, options = {}) {
const bgClass = getIdenticonBackgroundClass(entity.id);
const title = getIdenticonTitle(entity.name);
- return `<div class="avatar identicon ${_.escape(sizeClass)} ${_.escape(bgClass)}">${_.escape(title)}</div>`;
+ return `<div class="avatar identicon ${_.escape(sizeClass)} ${_.escape(bgClass)}">${_.escape(
+ title,
+ )}</div>`;
}
export function renderAvatar(entity, options = {}) {
diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
index e2be805ed22..ec759043efc 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue
@@ -25,14 +25,32 @@ export default {
},
},
methods: {
- createFile(target, file, isText) {
+ isText(content, fileType) {
+ const knownBinaryFileTypes = ['image/'];
+ const knownTextFileTypes = ['text/'];
+ const isKnownBinaryFileType = knownBinaryFileTypes.find(type => fileType.includes(type));
+ const isKnownTextFileType = knownTextFileTypes.find(type => fileType.includes(type));
+ const asciiRegex = /^[ -~\t\n\r]+$/; // tests whether a string contains ascii characters only (ranges from space to tilde, tabs and new lines)
+
+ if (isKnownBinaryFileType) {
+ return false;
+ }
+
+ if (isKnownTextFileType) {
+ return true;
+ }
+
+ // if it's not a known file type, determine the type by evaluating the file contents
+ return asciiRegex.test(content);
+ },
+ createFile(target, file) {
const { name } = file;
let { result } = target;
+ const encodedContent = result.split('base64,')[1];
+ const rawContent = encodedContent ? atob(encodedContent) : '';
+ const isText = this.isText(rawContent, file.type);
- if (!isText) {
- // eslint-disable-next-line prefer-destructuring
- result = result.split('base64,')[1];
- }
+ result = isText ? rawContent : encodedContent;
this.$emit('create', {
name: `${this.path ? `${this.path}/` : ''}${name}`,
@@ -43,15 +61,9 @@ export default {
},
readFile(file) {
const reader = new FileReader();
- const isText = file.type.match(/text.*/) !== null;
- reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true });
-
- if (isText) {
- reader.readAsText(file);
- } else {
- reader.readAsDataURL(file);
- }
+ reader.addEventListener('load', e => this.createFile(e.target, file), { once: true });
+ reader.readAsDataURL(file);
},
openFile() {
Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file));
diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js
index fab0255c378..3587f073a00 100644
--- a/app/assets/javascripts/image_diff/image_diff.js
+++ b/app/assets/javascripts/image_diff/image_diff.js
@@ -60,8 +60,10 @@ export default class ImageDiff {
}
renderBadge(discussionEl, index) {
- const imageBadge = imageDiffHelper
- .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl);
+ const imageBadge = imageDiffHelper.generateBadgeFromDiscussionDOM(
+ this.imageFrameEl,
+ discussionEl,
+ );
this.imageBadges.push(imageBadge);
diff --git a/app/assets/javascripts/image_diff/init_discussion_tab.js b/app/assets/javascripts/image_diff/init_discussion_tab.js
index 2f16c6ef115..dbe4c06a4e9 100644
--- a/app/assets/javascripts/image_diff/init_discussion_tab.js
+++ b/app/assets/javascripts/image_diff/init_discussion_tab.js
@@ -8,5 +8,6 @@ export default () => {
const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file');
[...diffFileEls].forEach(diffFileEl =>
- imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge));
+ imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge),
+ );
};
diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js
index 4abd13fb472..8d9e65155d8 100644
--- a/app/assets/javascripts/image_diff/replaced_image_diff.js
+++ b/app/assets/javascripts/image_diff/replaced_image_diff.js
@@ -26,7 +26,7 @@ export default class ReplacedImageDiff extends ImageDiff {
this.imageEls = {};
const viewTypeNames = Object.getOwnPropertyNames(viewTypes);
- viewTypeNames.forEach((viewType) => {
+ viewTypeNames.forEach(viewType => {
this.imageEls[viewType] = this.imageFrameEls[viewType].querySelector('img');
});
}
@@ -79,13 +79,12 @@ export default class ReplacedImageDiff extends ImageDiff {
// Re-render indicator in new view
if (indicator.removed) {
- const normalizedIndicator = imageDiffHelper
- .resizeCoordinatesToImageElement(this.imageEl, {
- x: indicator.x,
- y: indicator.y,
- width: indicator.image.width,
- height: indicator.image.height,
- });
+ const normalizedIndicator = imageDiffHelper.resizeCoordinatesToImageElement(this.imageEl, {
+ x: indicator.x,
+ y: indicator.y,
+ width: indicator.image.width,
+ height: indicator.image.height,
+ });
imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator);
}
}
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index eda8cdad908..f1beb1a8ea5 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -60,66 +60,71 @@ class ImporterStatus {
attributes = Object.assign(repoData, attributes);
}
- return axios.post(this.importUrl, attributes)
- .then(({ data }) => {
- const job = $(`tr#repo_${id}`);
- job.attr('id', `project_${data.id}`);
-
- job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
- $('table.import-jobs tbody').prepend(job);
-
- job.addClass('table-active');
- const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing');
- job.find('.import-actions').html(sprintf(
- _.escape(__('%{loadingIcon} Started')), {
- loadingIcon: `<i class="fa fa-spinner fa-spin" aria-label="${_.escape(connectingVerb)}"></i>`,
- },
- false,
- ));
- })
- .catch((error) => {
- let details = error;
-
- const $statusField = $(`#repo_${this.id} .job-status`);
- $statusField.text(__('Failed'));
-
- if (error.response && error.response.data && error.response.data.errors) {
- details = error.response.data.errors;
- }
-
- flash(sprintf(__('An error occurred while importing project: %{details}'), { details }));
- });
+ return axios
+ .post(this.importUrl, attributes)
+ .then(({ data }) => {
+ const job = $(`tr#repo_${id}`);
+ job.attr('id', `project_${data.id}`);
+
+ job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
+ $('table.import-jobs tbody').prepend(job);
+
+ job.addClass('table-active');
+ const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing');
+ job.find('.import-actions').html(
+ sprintf(
+ _.escape(__('%{loadingIcon} Started')),
+ {
+ loadingIcon: `<i class="fa fa-spinner fa-spin" aria-label="${_.escape(
+ connectingVerb,
+ )}"></i>`,
+ },
+ false,
+ ),
+ );
+ })
+ .catch(error => {
+ let details = error;
+
+ const $statusField = $(`#repo_${this.id} .job-status`);
+ $statusField.text(__('Failed'));
+
+ if (error.response && error.response.data && error.response.data.errors) {
+ details = error.response.data.errors;
+ }
+
+ flash(sprintf(__('An error occurred while importing project: %{details}'), { details }));
+ });
}
autoUpdate() {
- return axios.get(this.jobsUrl)
- .then(({ data = [] }) => {
- data.forEach((job) => {
- const jobItem = $(`#project_${job.id}`);
- const statusField = jobItem.find('.job-status');
-
- const spinner = '<i class="fa fa-spinner fa-spin"></i>';
-
- switch (job.import_status) {
- case 'finished':
- jobItem.removeClass('table-active').addClass('table-success');
- statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`);
- break;
- case 'scheduled':
- statusField.html(`${spinner} ${__('Scheduled')}`);
- break;
- case 'started':
- statusField.html(`${spinner} ${__('Started')}`);
- break;
- case 'failed':
- statusField.html(__('Failed'));
- break;
- default:
- statusField.html(job.import_status);
- break;
- }
- });
+ return axios.get(this.jobsUrl).then(({ data = [] }) => {
+ data.forEach(job => {
+ const jobItem = $(`#project_${job.id}`);
+ const statusField = jobItem.find('.job-status');
+
+ const spinner = '<i class="fa fa-spinner fa-spin"></i>';
+
+ switch (job.import_status) {
+ case 'finished':
+ jobItem.removeClass('table-active').addClass('table-success');
+ statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`);
+ break;
+ case 'scheduled':
+ statusField.html(`${spinner} ${__('Scheduled')}`);
+ break;
+ case 'started':
+ statusField.html(`${spinner} ${__('Started')}`);
+ break;
+ case 'failed':
+ statusField.html(__('Failed'));
+ break;
+ default:
+ statusField.html(job.import_status);
+ break;
+ }
});
+ });
}
setAutoUpdate() {
@@ -141,7 +146,4 @@ function initImporterStatus() {
}
}
-export {
- initImporterStatus as default,
- ImporterStatus,
-};
+export { initImporterStatus as default, ImporterStatus };
diff --git a/app/assets/javascripts/init_changes_dropdown.js b/app/assets/javascripts/init_changes_dropdown.js
index 5c5a6e01848..e708e5d0978 100644
--- a/app/assets/javascripts/init_changes_dropdown.js
+++ b/app/assets/javascripts/init_changes_dropdown.js
@@ -1,7 +1,7 @@
import $ from 'jquery';
import { stickyMonitor } from './lib/utils/sticky';
-export default (stickyTop) => {
+export default stickyTop => {
stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop);
$('.js-diff-stats-dropdown').glDropdown({
diff --git a/app/assets/javascripts/init_notes.js b/app/assets/javascripts/init_notes.js
index 3c71258e53b..a77828e8cf2 100644
--- a/app/assets/javascripts/init_notes.js
+++ b/app/assets/javascripts/init_notes.js
@@ -2,13 +2,7 @@ import Notes from './notes';
export default () => {
const dataEl = document.querySelector('.js-notes-data');
- const {
- notesUrl,
- notesIds,
- now,
- diffView,
- enableGFM,
- } = JSON.parse(dataEl.innerHTML);
+ const { notesUrl, notesIds, now, diffView, enableGFM } = JSON.parse(dataEl.innerHTML);
// Create a singleton so that we don't need to assign
// into the window object, we can just access the current isntance with Notes.instance
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index bd90d0eaa32..08b858305ab 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -97,7 +97,8 @@ export default class IntegrationSettingsForm {
testSettings(formData) {
this.toggleSubmitBtnState(true);
- return axios.put(this.testEndPoint, formData)
+ return axios
+ .put(this.testEndPoint, formData)
.then(({ data }) => {
if (data.error) {
let flashActions;
@@ -105,7 +106,7 @@ export default class IntegrationSettingsForm {
if (data.test_failed) {
flashActions = {
title: 'Save anyway',
- clickHandler: (e) => {
+ clickHandler: e => {
e.preventDefault();
this.$form.submit();
},
diff --git a/app/assets/javascripts/issuable/auto_width_dropdown_select.js b/app/assets/javascripts/issuable/auto_width_dropdown_select.js
index 07cf1eff279..612c524ca1c 100644
--- a/app/assets/javascripts/issuable/auto_width_dropdown_select.js
+++ b/app/assets/javascripts/issuable/auto_width_dropdown_select.js
@@ -27,7 +27,10 @@ class AutoWidthDropdownSelect {
// We have to look at the parent because
// `offsetParent` on a `display: none;` is `null`
- const offsetParentWidth = $(this).parent().offsetParent().width();
+ const offsetParentWidth = $(this)
+ .parent()
+ .offsetParent()
+ .width();
// Reset any width to let it naturally flow
$dropdown.css('width', 'auto');
if ($dropdown.outerWidth(false) > offsetParentWidth) {
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index 9848bcc2e64..b844e4c5e5b 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -32,7 +32,7 @@ export default {
onFormSubmitFailure() {
this.form.find('[type="submit"]').enable();
- return new Flash("Issue update failed");
+ return new Flash('Issue update failed');
},
getSelectedIssues() {
@@ -63,7 +63,7 @@ export default {
const result = [];
const labelsToKeep = this.$labelDropdown.data('indeterminate');
- this.getLabelsFromSelection().forEach((id) => {
+ this.getLabelsFromSelection().forEach(id => {
if (labelsToKeep.indexOf(id) === -1) {
result.push(id);
}
@@ -89,8 +89,8 @@ export default {
issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
add_label_ids: [],
- remove_label_ids: []
- }
+ remove_label_ids: [],
+ },
};
if (this.willUpdateLabels) {
formData.update.add_label_ids = this.$labelDropdown.data('marked');
@@ -134,7 +134,7 @@ export default {
// Collect unique label IDs for all checked issues
this.getElement('.selected-issuable:checked').each((i, el) => {
issuableLabels = this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels');
- issuableLabels.forEach((labelId) => {
+ issuableLabels.forEach(labelId => {
// Store unique IDs
if (uniqueIds.indexOf(labelId) === -1) {
uniqueIds.push(labelId);
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 0140960b367..c81a2230310 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -1,6 +1,3 @@
-/* eslint-disable no-new, no-unused-vars, consistent-return, no-else-return */
-/* global GitLab */
-
import $ from 'jquery';
import Pikaday from 'pikaday';
import Autosave from './autosave';
@@ -8,7 +5,7 @@ import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode';
import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
-import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
+import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
export default class IssuableForm {
constructor(form) {
@@ -19,9 +16,11 @@ export default class IssuableForm {
this.handleSubmit = this.handleSubmit.bind(this);
this.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i;
- new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup();
- new UsersSelect();
- new ZenMode();
+ this.gfmAutoComplete = new GfmAutoComplete(
+ gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources,
+ ).setup();
+ this.usersSelect = new UsersSelect();
+ this.zenMode = new ZenMode();
this.titleField = this.form.find('input[name*="[title]"]');
this.descriptionField = this.form.find('textarea[name*="[description]"]');
@@ -57,8 +56,16 @@ export default class IssuableForm {
}
initAutosave() {
- new Autosave(this.titleField, [document.location.pathname, document.location.search, 'title']);
- return new Autosave(this.descriptionField, [document.location.pathname, document.location.search, 'description']);
+ this.autosave = new Autosave(this.titleField, [
+ document.location.pathname,
+ document.location.search,
+ 'title',
+ ]);
+ return new Autosave(this.descriptionField, [
+ document.location.pathname,
+ document.location.search,
+ 'description',
+ ]);
}
handleSubmit() {
@@ -74,7 +81,7 @@ export default class IssuableForm {
this.$wipExplanation = this.form.find('.js-wip-explanation');
this.$noWipExplanation = this.form.find('.js-no-wip-explanation');
if (!(this.$wipExplanation.length && this.$noWipExplanation.length)) {
- return;
+ return undefined;
}
this.form.on('click', '.js-toggle-wip', this.toggleWip);
this.titleField.on('keyup blur', this.renderWipExplanation);
@@ -89,10 +96,9 @@ export default class IssuableForm {
if (this.workInProgress()) {
this.$wipExplanation.show();
return this.$noWipExplanation.hide();
- } else {
- this.$wipExplanation.hide();
- return this.$noWipExplanation.show();
}
+ this.$wipExplanation.hide();
+ return this.$noWipExplanation.show();
}
toggleWip(event) {
@@ -110,7 +116,7 @@ export default class IssuableForm {
}
addWip() {
- this.titleField.val(`WIP: ${(this.titleField.val())}`);
+ this.titleField.val(`WIP: ${this.titleField.val()}`);
}
initTargetBranchDropdown() {
diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js
index 06ec4546164..ffcbd7cf28c 100644
--- a/app/assets/javascripts/issuable_index.js
+++ b/app/assets/javascripts/issuable_index.js
@@ -26,14 +26,17 @@ export default class IssuableIndex {
static resetIncomingEmailToken() {
const $resetToken = $('.incoming-email-token-reset');
- $resetToken.on('click', (e) => {
+ $resetToken.on('click', e => {
e.preventDefault();
$resetToken.text('resetting...');
- axios.put($resetToken.attr('href'))
+ axios
+ .put($resetToken.attr('href'))
.then(({ data }) => {
- $('#issuable_email').val(data.new_address).focus();
+ $('#issuable_email')
+ .val(data.new_address)
+ .focus();
$resetToken.text('reset it');
})
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 4b4e9aa48ab..94b78907d9a 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -28,7 +28,7 @@ export default class Issue {
}
// Listen to state changes in the Vue app
- document.addEventListener('issuable_vue_app:change', (event) => {
+ document.addEventListener('issuable_vue_app:change', event => {
this.updateTopState(event.detail.isClosed, event.detail.data);
});
}
@@ -55,7 +55,13 @@ export default class Issue {
$(document).trigger('issuable:change', isClosed);
this.toggleCloseReopenButton(isClosed);
- let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, ''));
+ let numProjectIssues = Number(
+ projectIssuesCounter
+ .first()
+ .text()
+ .trim()
+ .replace(/[^\d]/, ''),
+ );
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
projectIssuesCounter.text(addDelimiter(numProjectIssues));
@@ -76,29 +82,34 @@ export default class Issue {
initIssueBtnEventListeners() {
const issueFailMessage = 'Unable to update this issue at this time.';
- return $(document).on('click', '.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen', (e) => {
- var $button, shouldSubmit, url;
- e.preventDefault();
- e.stopImmediatePropagation();
- $button = $(e.currentTarget);
- shouldSubmit = $button.hasClass('btn-comment');
- if (shouldSubmit) {
- Issue.submitNoteForm($button.closest('form'));
- }
-
- this.disableCloseReopenButton($button);
+ return $(document).on(
+ 'click',
+ '.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen',
+ e => {
+ var $button, shouldSubmit, url;
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ $button = $(e.currentTarget);
+ shouldSubmit = $button.hasClass('btn-comment');
+ if (shouldSubmit) {
+ Issue.submitNoteForm($button.closest('form'));
+ }
- url = $button.attr('href');
- return axios.put(url)
- .then(({ data }) => {
- const isClosed = $button.hasClass('btn-close');
- this.updateTopState(isClosed, data);
- })
- .catch(() => flash(issueFailMessage))
- .then(() => {
- this.disableCloseReopenButton($button, false);
- });
- });
+ this.disableCloseReopenButton($button);
+
+ url = $button.attr('href');
+ return axios
+ .put(url)
+ .then(({ data }) => {
+ const isClosed = $button.hasClass('btn-close');
+ this.updateTopState(isClosed, data);
+ })
+ .catch(() => flash(issueFailMessage))
+ .then(() => {
+ this.disableCloseReopenButton($button, false);
+ });
+ },
+ );
}
initCloseReopenReport() {
@@ -124,7 +135,7 @@ export default class Issue {
static submitNoteForm(form) {
var noteText;
- noteText = form.find("textarea.js-note-text").val();
+ noteText = form.find('textarea.js-note-text').val();
if (noteText && noteText.trim().length > 0) {
return form.submit();
}
@@ -133,22 +144,26 @@ export default class Issue {
static initMergeRequests() {
var $container;
$container = $('#merge-requests');
- return axios.get($container.data('url'))
+ return axios
+ .get($container.data('url'))
.then(({ data }) => {
if ('html' in data) {
$container.html(data.html);
}
- }).catch(() => flash('Failed to load referenced merge requests'));
+ })
+ .catch(() => flash('Failed to load referenced merge requests'));
}
static initRelatedBranches() {
var $container;
$container = $('#related-branches');
- return axios.get($container.data('url'))
+ return axios
+ .get($container.data('url'))
.then(({ data }) => {
if ('html' in data) {
$container.html(data.html);
}
- }).catch(() => flash('Failed to load related branches'));
+ })
+ .catch(() => flash('Failed to load related branches'));
}
}
diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js
deleted file mode 100644
index 854445bd2a4..00000000000
--- a/app/assets/javascripts/job.js
+++ /dev/null
@@ -1,190 +0,0 @@
-import $ from 'jquery';
-import _ from 'underscore';
-import { polyfillSticky } from './lib/utils/sticky';
-import axios from './lib/utils/axios_utils';
-import { visitUrl } from './lib/utils/url_utility';
-import bp from './breakpoints';
-import { numberToHumanSize } from './lib/utils/number_utils';
-import { setCiStatusFavicon } from './lib/utils/common_utils';
-import { isScrolledToBottom, scrollDown, scrollUp } from './lib/utils/scroll_utils';
-import LogOutputBehaviours from './lib/utils/logoutput_behaviours';
-
-export default class Job extends LogOutputBehaviours {
- constructor(options) {
- super();
- this.timeout = null;
- this.state = null;
- this.fetchingStatusFavicon = false;
- this.options = options || $('.js-build-options').data();
-
- this.pagePath = this.options.pagePath;
- this.buildStatus = this.options.buildStatus;
- this.state = this.options.logState;
- this.buildStage = this.options.buildStage;
- this.$document = $(document);
- this.$window = $(window);
- this.logBytes = 0;
-
- this.$buildTrace = $('#build-trace');
- this.$buildRefreshAnimation = $('.js-build-refresh');
- this.$truncatedInfo = $('.js-truncated-info');
- this.$buildTraceOutput = $('.js-build-output');
- this.$topBar = $('.js-top-bar');
-
- clearTimeout(this.timeout);
-
- this.initSidebar();
- this.sidebarOnResize();
-
- this.$document
- .off('click', '.js-sidebar-build-toggle')
- .on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));
-
- this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
-
- this.$window
- .off('scroll')
- .on('scroll', () => {
- if (!isScrolledToBottom()) {
- this.toggleScrollAnimation(false);
- } else if (isScrolledToBottom() && !this.isLogComplete) {
- this.toggleScrollAnimation(true);
- }
- this.scrollThrottled();
- });
-
- this.$window
- .off('resize.build')
- .on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100));
-
- this.initAffixTopArea();
-
- this.getBuildTrace();
- }
-
- initAffixTopArea() {
- polyfillSticky(this.$topBar);
- }
-
- scrollToBottom() {
- scrollDown();
- this.hasBeenScrolled = true;
- this.toggleScroll();
- }
-
- scrollToTop() {
- scrollUp();
- this.hasBeenScrolled = true;
- this.toggleScroll();
- }
-
- toggleScrollAnimation(toggle) {
- this.$scrollBottomBtn.toggleClass('animate', toggle);
- }
-
- initSidebar() {
- this.$sidebar = $('.js-build-sidebar');
- }
-
- getBuildTrace() {
- return axios.get(`${this.pagePath}/trace.json`, {
- params: { state: this.state },
- })
- .then((res) => {
- const log = res.data;
-
- if (!this.fetchingStatusFavicon) {
- this.fetchingStatusFavicon = true;
-
- setCiStatusFavicon(`${this.pagePath}/status.json`)
- .then(() => {
- this.fetchingStatusFavicon = false;
- })
- .catch(() => {
- this.fetchingStatusFavicon = false;
- });
- }
-
- if (log.state) {
- this.state = log.state;
- }
-
- this.isScrollInBottom = isScrolledToBottom();
-
- if (log.append) {
- this.$buildTraceOutput.append(log.html);
- this.logBytes += log.size;
- } else {
- this.$buildTraceOutput.html(log.html);
- this.logBytes = log.size;
- }
-
- // if the incremental sum of logBytes we received is less than the total
- // we need to show a message warning the user about that.
- if (this.logBytes < log.total) {
- // size is in bytes, we need to calculate KiB
- const size = numberToHumanSize(this.logBytes);
- $('.js-truncated-info-size').html(`${size}`);
- this.$truncatedInfo.removeClass('hidden');
- } else {
- this.$truncatedInfo.addClass('hidden');
- }
- this.isLogComplete = log.complete;
-
- if (log.complete === false) {
- this.timeout = setTimeout(() => {
- this.getBuildTrace();
- }, 4000);
- } else {
- this.$buildRefreshAnimation.remove();
- this.toggleScrollAnimation(false);
- }
-
- if (log.status !== this.buildStatus) {
- visitUrl(this.pagePath);
- }
- })
- .catch(() => {
- this.$buildRefreshAnimation.remove();
- })
- .then(() => {
- if (this.isScrollInBottom) {
- scrollDown();
- }
- })
- .then(() => this.toggleScroll());
- }
- // eslint-disable-next-line class-methods-use-this
- shouldHideSidebarForViewport() {
- const bootstrapBreakpoint = bp.getBreakpointSize();
- return bootstrapBreakpoint === 'xs';
- }
-
- toggleSidebar(shouldHide) {
- const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;
- const $toggleButton = $('.js-sidebar-build-toggle-header');
-
- this.$sidebar
- .toggleClass('right-sidebar-expanded', shouldShow)
- .toggleClass('right-sidebar-collapsed', shouldHide);
-
- this.$topBar
- .toggleClass('sidebar-expanded', shouldShow)
- .toggleClass('sidebar-collapsed', shouldHide);
-
- if (this.$sidebar.hasClass('right-sidebar-expanded')) {
- $toggleButton.addClass('hidden');
- } else {
- $toggleButton.removeClass('hidden');
- }
- }
-
- sidebarOnResize() {
- this.toggleSidebar(this.shouldHideSidebarForViewport());
- }
-
- sidebarOnClick() {
- if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
- }
-
-}
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 4e8d3ad24cc..ac19034f69d 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -1,21 +1,32 @@
<script>
- import { mapGetters, mapState } from 'vuex';
+ import _ from 'underscore';
+ import { mapGetters, mapState, mapActions } from 'vuex';
+ import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
+ import bp from '~/breakpoints';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
+ import createStore from '../store';
import EmptyState from './empty_state.vue';
import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue';
+ import Log from './job_log.vue';
+ import LogTopBar from './job_log_controllers.vue';
import StuckBlock from './stuck_block.vue';
+ import Sidebar from './sidebar.vue';
export default {
name: 'JobPageApp',
+ store: createStore(),
components: {
CiHeader,
Callout,
EmptyState,
EnvironmentsBlock,
ErasedBlock,
+ Log,
+ LogTopBar,
StuckBlock,
+ Sidebar,
},
props: {
runnerSettingsUrl: {
@@ -23,19 +34,129 @@
required: false,
default: null,
},
+ runnerHelpUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ endpoint: {
+ type: String,
+ required: true,
+ },
+ terminalPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ pagePath: {
+ type: String,
+ required: true,
+ },
+ logState: {
+ type: String,
+ required: true,
+ },
},
computed: {
- ...mapState(['isLoading', 'job']),
+ ...mapState([
+ 'isLoading',
+ 'job',
+ 'isSidebarOpen',
+ 'trace',
+ 'isTraceComplete',
+ 'traceSize',
+ 'isTraceSizeVisible',
+ 'isScrollBottomDisabled',
+ 'isScrollTopDisabled',
+ 'isScrolledToBottomBeforeReceivingTrace',
+ 'hasError',
+ ]),
...mapGetters([
'headerActions',
'headerTime',
'shouldRenderCalloutMessage',
'shouldRenderTriggeredLabel',
'hasEnvironment',
- 'isJobStuck',
'hasTrace',
'emptyStateIllustration',
+ 'isScrollingDown',
+ 'emptyStateAction',
+ 'hasRunnersForProject',
]),
+
+ shouldRenderContent() {
+ return !this.isLoading && !this.hasError;
+ }
+ },
+ watch: {
+ // Once the job log is loaded,
+ // fetch the stages for the dropdown on the sidebar
+ job(newVal, oldVal) {
+ if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) {
+ this.fetchStages();
+ }
+ },
+ },
+ created() {
+ this.throttled = _.throttle(this.toggleScrollButtons, 100);
+
+ this.setJobEndpoint(this.endpoint);
+ this.setTraceOptions({
+ logState: this.logState,
+ pagePath: this.pagePath,
+ });
+
+ this.fetchJob();
+ this.fetchTrace();
+
+ window.addEventListener('resize', this.onResize);
+ window.addEventListener('scroll', this.updateScroll);
+ },
+
+ mounted() {
+ this.updateSidebar();
+ },
+
+ destroyed() {
+ window.removeEventListener('resize', this.onResize);
+ window.removeEventListener('scroll', this.updateScroll);
+ },
+
+ methods: {
+ ...mapActions([
+ 'setJobEndpoint',
+ 'setTraceOptions',
+ 'fetchJob',
+ 'fetchStages',
+ 'hideSidebar',
+ 'showSidebar',
+ 'toggleSidebar',
+ 'fetchTrace',
+ 'scrollBottom',
+ 'scrollTop',
+ 'toggleScrollButtons',
+ 'toggleScrollAnimation',
+ ]),
+ onResize() {
+ this.updateSidebar();
+ this.updateScroll();
+ },
+ updateSidebar() {
+ if (bp.getBreakpointSize() === 'xs') {
+ this.hideSidebar();
+ } else if (!this.isSidebarOpen) {
+ this.showSidebar();
+ }
+ },
+ updateScroll() {
+ if (!isScrolledToBottom()) {
+ this.toggleScrollAnimation(false);
+ } else if (this.isScrollingDown) {
+ this.toggleScrollAnimation(true);
+ }
+
+ this.throttled();
+ },
},
};
</script>
@@ -44,71 +165,107 @@
<gl-loading-icon
v-if="isLoading"
:size="2"
- class="prepend-top-20"
+ class="js-job-loading qa-loading-animation prepend-top-20"
/>
- <template v-else>
- <!-- Header Section -->
- <header>
- <div class="js-build-header build-header top-area">
- <ci-header
- :status="job.status"
- :item-id="job.id"
- :time="headerTime"
- :user="job.user"
- :actions="headerActions"
- :has-sidebar-button="true"
- :should-render-triggered-label="shouldRenderTriggeredLabel"
- :item-name="__('Job')"
+ <template v-else-if="shouldRenderContent">
+ <div class="js-job-content build-page">
+ <!-- Header Section -->
+ <header>
+ <div class="js-build-header build-header top-area">
+ <ci-header
+ :status="job.status"
+ :item-id="job.id"
+ :time="headerTime"
+ :user="job.user"
+ :actions="headerActions"
+ :has-sidebar-button="true"
+ :should-render-triggered-label="shouldRenderTriggeredLabel"
+ :item-name="__('Job')"
+ @clickedSidebarButton="toggleSidebar"
+ />
+ </div>
+
+ <callout
+ v-if="shouldRenderCalloutMessage"
+ :message="job.callout_message"
+ />
+ </header>
+ <!-- EO Header Section -->
+
+ <!-- Body Section -->
+ <stuck-block
+ v-if="job.stuck"
+ class="js-job-stuck"
+ :has-no-runners-for-project="hasRunnersForProject"
+ :tags="job.tags"
+ :runners-path="runnerSettingsUrl"
+ />
+
+ <environments-block
+ v-if="hasEnvironment"
+ class="js-job-environment"
+ :deployment-status="job.deployment_status"
+ :icon-status="job.status"
+ />
+
+ <erased-block
+ v-if="job.erased_at"
+ class="js-job-erased-block"
+ :user="job.erased_by"
+ :erased-at="job.erased_at"
+ />
+
+ <!--job log -->
+ <div
+ v-if="hasTrace"
+ class="build-trace-container prepend-top-default">
+ <log-top-bar
+ :class="{
+ 'sidebar-expanded': isSidebarOpen,
+ 'sidebar-collapsed': !isSidebarOpen
+ }"
+ :erase-path="job.erase_path"
+ :size="traceSize"
+ :raw-path="job.raw_path"
+ :is-scroll-bottom-disabled="isScrollBottomDisabled"
+ :is-scroll-top-disabled="isScrollTopDisabled"
+ :is-trace-size-visible="isTraceSizeVisible"
+ :is-scrolling-down="isScrollingDown"
+ @scrollJobLogTop="scrollTop"
+ @scrollJobLogBottom="scrollBottom"
+ />
+ <log
+ :trace="trace"
+ :is-complete="isTraceComplete"
/>
</div>
+ <!-- EO job log -->
- <callout
- v-if="shouldRenderCalloutMessage"
- :message="job.callout_message"
+ <!--empty state -->
+ <empty-state
+ v-if="!hasTrace"
+ class="js-job-empty-state"
+ :illustration-path="emptyStateIllustration.image"
+ :illustration-size-class="emptyStateIllustration.size"
+ :title="emptyStateIllustration.title"
+ :content="emptyStateIllustration.content"
+ :action="emptyStateAction"
/>
- </header>
- <!-- EO Header Section -->
-
- <!-- Body Section -->
- <stuck-block
- v-if="isJobStuck"
- class="js-job-stuck"
- :has-no-runners-for-project="job.runners.available"
- :tags="job.tags"
- :runners-path="runnerSettingsUrl"
- />
-
- <environments-block
- v-if="hasEnvironment"
- class="js-job-environment"
- :deployment-status="job.deployment_status"
- :icon-status="job.status"
- />
-
- <erased-block
- v-if="job.erased_at"
- class="js-job-erased-block"
- :user="job.erased_by"
- :erased-at="job.erased_at"
- />
-
- <!--job log -->
- <!-- EO job log -->
-
- <!--empty state -->
- <empty-state
- v-if="!hasTrace"
- class="js-job-empty-state"
- :illustration-path="emptyStateIllustration.image"
- :illustration-size-class="emptyStateIllustration.size"
- :title="emptyStateIllustration.title"
- :content="emptyStateIllustration.content"
- :action="job.status.action"
- />
<!-- EO empty state -->
<!-- EO Body Section -->
+ </div>
</template>
+
+ <sidebar
+ v-if="shouldRenderContent"
+ class="js-job-sidebar"
+ :class="{
+ 'right-sidebar-expanded': isSidebarOpen,
+ 'right-sidebar-collapsed': !isSidebarOpen
+ }"
+ :runner-help-url="runnerHelpUrl"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue
new file mode 100644
index 00000000000..6486b25c8a7
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_container_item.vue
@@ -0,0 +1,65 @@
+<script>
+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,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ isActive: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ tooltipText() {
+ return `${this.job.name} - ${this.job.status.tooltip}`;
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="build-job"
+ :class="{
+ retried: job.retried,
+ active: isActive
+ }"
+ >
+ <a
+ v-tooltip
+ :href="job.status.details_path"
+ :title="tooltipText"
+ data-container="body"
+ data-boundary="viewport"
+ class="js-job-link"
+ >
+ <icon
+ v-if="isActive"
+ name="arrow-right"
+ class="js-arrow-right icon-arrow-right"
+ />
+
+ <ci-icon :status="job.status" />
+
+ <span>{{ job.name ? job.name : job.id }}</span>
+
+ <icon
+ v-if="job.retried"
+ name="retry"
+ class="js-retry-icon"
+ />
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
index 9d78d89239a..ffa6ada3e28 100644
--- a/app/assets/javascripts/jobs/components/job_log.vue
+++ b/app/assets/javascripts/jobs/components/job_log.vue
@@ -1,20 +1,48 @@
<script>
-export default {
- name: 'JobLog',
- props: {
- trace: {
- type: String,
- required: true,
+ import { mapState, mapActions } from 'vuex';
+
+ export default {
+ name: 'JobLog',
+ props: {
+ trace: {
+ type: String,
+ required: true,
+ },
+ isComplete: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['isScrolledToBottomBeforeReceivingTrace']),
+ },
+ updated() {
+ this.$nextTick(() => this.handleScrollDown());
+ },
+ mounted() {
+ this.$nextTick(() => this.handleScrollDown());
},
- isComplete: {
- type: Boolean,
- required: true,
+ methods: {
+ ...mapActions(['scrollBottom']),
+ /**
+ * The job log is sent in HTML, which means we need to use `v-html` to render it
+ * Using the updated hook with $nextTick is not enough to wait for the DOM to be updated
+ * in this case because it runs before `v-html` has finished running, since there's no
+ * Vue binding.
+ * In order to scroll the page down after `v-html` has finished, we need to use setTimeout
+ */
+ handleScrollDown() {
+ if (this.isScrolledToBottomBeforeReceivingTrace) {
+ setTimeout(() => {
+ this.scrollBottom();
+ }, 0);
+ }
+ },
},
- },
-};
+ };
</script>
<template>
- <pre class="build-trace">
+ <pre class="js-build-trace build-trace qa-build-trace">
<code
class="bash"
v-html="trace"
@@ -22,7 +50,7 @@ export default {
</code>
<div
- v-if="isComplete"
+ v-if="!isComplete"
class="js-log-animation build-loader-animation"
>
<div class="dot"></div>
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index cc885ea8e1b..94ab1b16c84 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -4,6 +4,7 @@ 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 scrollDown from '../svg/scroll_down.svg';
export default {
components: {
@@ -12,6 +13,7 @@ export default {
directives: {
tooltip,
},
+ scrollDown,
props: {
erasePath: {
type: String,
@@ -65,7 +67,7 @@ export default {
};
</script>
<template>
- <div class="top-bar affix js-top-bar">
+ <div class="top-bar affix">
<!-- truncate information -->
<div class="js-truncated-info truncated-info d-none d-sm-block float-left">
<template v-if="isTraceSizeVisible">
@@ -100,7 +102,7 @@ export default {
v-tooltip
:title="s__('Job|Erase job log')"
:href="erasePath"
- data-confirm="__('Are you sure you want to erase this build?')"
+ :data-confirm="__('Are you sure you want to erase this build?')"
class="js-erase-link controllers-buttons"
data-container="body"
data-method="post"
@@ -138,8 +140,8 @@ export default {
class="js-scroll-bottom btn-scroll btn-transparent btn-blank"
:class="{ animate: isScrollingDown }"
@click="handleScrollToBottom"
+ v-html="$options.scrollDown"
>
- <icon name="scroll_down"/>
</button>
</div>
<!-- eo scroll buttons -->
diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue
index 03f36ec5c8b..951bcb36600 100644
--- a/app/assets/javascripts/jobs/components/jobs_container.vue
+++ b/app/assets/javascripts/jobs/components/jobs_container.vue
@@ -1,17 +1,11 @@
<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 JobContainerItem from './job_container_item.vue';
export default {
components: {
- CiIcon,
- Icon,
- },
- directives: {
- tooltip,
+ JobContainerItem,
},
+
props: {
jobs: {
type: Array,
@@ -26,49 +20,16 @@ export default {
isJobActive(currentJobId) {
return this.jobId === currentJobId;
},
- tooltipText(job) {
- return `${_.escape(job.name)} - ${job.status.tooltip}`;
- },
},
};
</script>
<template>
<div class="js-jobs-container builds-container">
- <div
+ <job-container-item
v-for="job in jobs"
:key="job.id"
- class="build-job"
- :class="{ retried: job.retried, active: isJobActive(job.id) }"
- >
- <a
- v-tooltip
- :href="job.status.details_path"
- :title="tooltipText(job)"
- data-container="body"
- >
- <icon
- v-if="isJobActive(job.id)"
- name="arrow-right"
- class="js-arrow-right icon-arrow-right"
- />
-
- <ci-icon :status="job.status" />
-
- <span>
- <template v-if="job.name">
- {{ job.name }}
- </template>
- <template v-else>
- {{ job.id }}
- </template>
- </span>
-
- <icon
- v-if="job.retried"
- name="retry"
- class="js-retry-icon"
- />
- </a>
- </div>
+ :job="job"
+ :is-active="isJobActive(job.id)"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index 8f3c6aced23..906769ee6a2 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -29,14 +29,9 @@ export default {
required: false,
default: '',
},
- terminalPath: {
- type: String,
- required: false,
- default: null,
- },
},
computed: {
- ...mapState(['job', 'isLoading', 'stages', 'jobs', 'selectedStage']),
+ ...mapState(['job', 'stages', 'jobs', 'selectedStage']),
coverage() {
return `${this.job.coverage}%`;
},
@@ -64,10 +59,10 @@ export default {
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;
},
@@ -100,196 +95,190 @@ export default {
);
},
commit() {
- return this.job.pipeline.commit || {};
+ return this.job.pipeline && this.job.pipeline.commit ? this.job.pipeline.commit : {};
},
},
methods: {
- ...mapActions(['fetchJobsForStage']),
+ ...mapActions(['fetchJobsForStage', 'toggleSidebar']),
},
};
</script>
<template>
<aside
- class="js-build-sidebar right-sidebar right-sidebar-expanded build-sidebar"
+ class="right-sidebar build-sidebar"
data-offset-top="101"
data-spy="affix"
>
<div class="sidebar-container">
<div class="blocks-container">
- <template v-if="!isLoading">
- <div class="block">
- <strong class="inline prepend-top-8">
- {{ job.name }}
- </strong>
- <a
- v-if="job.retry_path"
- :class="retryButtonClass"
- :href="job.retry_path"
- data-method="post"
- rel="nofollow"
- >
- {{ __('Retry') }}
- </a>
- <a
- v-if="terminalPath"
- :href="terminalPath"
- class="js-terminal-link pull-right btn btn-primary
- btn-inverted visible-md-block visible-lg-block"
- target="_blank"
- >
- {{ __('Debug') }}
- <icon name="external-link" />
- </a>
- <button
- :aria-label="__('Toggle Sidebar')"
- type="button"
- class="btn btn-blank gutter-toggle
- float-right d-block d-md-none js-sidebar-build-toggle"
- >
- <i
- aria-hidden="true"
- data-hidden="true"
- class="fa fa-angle-double-right"
- ></i>
- </button>
- </div>
- <div
- v-if="job.retry_path || job.new_issue_path"
- class="block retry-link"
+ <div class="block">
+ <strong class="inline prepend-top-8">
+ {{ job.name }}
+ </strong>
+ <a
+ v-if="job.retry_path"
+ :class="retryButtonClass"
+ :href="job.retry_path"
+ data-method="post"
+ rel="nofollow"
>
- <a
- v-if="job.new_issue_path"
- :href="job.new_issue_path"
- class="js-new-issue btn btn-success btn-inverted"
- >
- {{ __('New issue') }}
+ {{ __('Retry') }}
+ </a>
+ <a
+ v-if="job.terminal_path"
+ :href="job.terminal_path"
+ class="js-terminal-link pull-right btn btn-primary
+ btn-inverted visible-md-block visible-lg-block"
+ target="_blank"
+ >
+ {{ __('Debug') }}
+ <icon name="external-link" />
+ </a>
+ <button
+ :aria-label="__('Toggle Sidebar')"
+ type="button"
+ class="btn btn-blank gutter-toggle
+ float-right d-block d-md-none js-sidebar-build-toggle"
+ @click="toggleSidebar"
+ >
+ <i
+ aria-hidden="true"
+ data-hidden="true"
+ class="fa fa-angle-double-right"
+ ></i>
+ </button>
+ </div>
+ <div
+ v-if="job.retry_path || job.new_issue_path"
+ class="block retry-link"
+ >
+ <a
+ v-if="job.new_issue_path"
+ :href="job.new_issue_path"
+ class="js-new-issue btn btn-success btn-inverted"
+ >
+ {{ __('New issue') }}
+ </a>
+ <a
+ v-if="job.retry_path"
+ :href="job.retry_path"
+ class="js-retry-job btn btn-inverted-secondary"
+ data-method="post"
+ rel="nofollow"
+ >
+ {{ __('Retry') }}
+ </a>
+ </div>
+ <div :class="{ block : renderBlock }">
+ <p
+ v-if="job.merge_request"
+ class="build-detail-row js-job-mr"
+ >
+ <span class="build-light-text">
+ {{ __('Merge Request:') }}
+ </span>
+ <a :href="job.merge_request.path">
+ !{{ job.merge_request.iid }}
</a>
+ </p>
+
+ <detail-row
+ v-if="job.duration"
+ :value="duration"
+ class="js-job-duration"
+ title="Duration"
+ />
+ <detail-row
+ v-if="job.finished_at"
+ :value="timeFormated(job.finished_at)"
+ class="js-job-finished"
+ title="Finished"
+ />
+ <detail-row
+ v-if="job.erased_at"
+ :value="timeFormated(job.erased_at)"
+ class="js-job-erased"
+ title="Erased"
+ />
+ <detail-row
+ v-if="job.queued"
+ :value="queued"
+ class="js-job-queued"
+ title="Queued"
+ />
+ <detail-row
+ v-if="hasTimeout"
+ :help-url="runnerHelpUrl"
+ :value="timeout"
+ class="js-job-timeout"
+ title="Timeout"
+ />
+ <detail-row
+ v-if="job.runner"
+ :value="runnerId"
+ class="js-job-runner"
+ title="Runner"
+ />
+ <detail-row
+ v-if="job.coverage"
+ :value="coverage"
+ class="js-job-coverage"
+ title="Coverage"
+ />
+ <p
+ v-if="job.tags.length"
+ class="build-detail-row js-job-tags"
+ >
+ <span class="build-light-text">
+ {{ __('Tags:') }}
+ </span>
+ <span
+ v-for="(tag, i) in job.tags"
+ :key="i"
+ class="label label-primary">
+ {{ tag }}
+ </span>
+ </p>
+
+ <div
+ v-if="job.cancel_path"
+ class="btn-group prepend-top-5"
+ role="group">
<a
- v-if="job.retry_path"
- :href="job.retry_path"
- class="js-retry-job btn btn-inverted-secondary"
+ :href="job.cancel_path"
+ class="js-cancel-job btn btn-sm btn-default"
data-method="post"
rel="nofollow"
>
- {{ __('Retry') }}
+ {{ __('Cancel') }}
</a>
</div>
- <div :class="{ block : renderBlock }">
- <p
- v-if="job.merge_request"
- class="build-detail-row js-job-mr"
- >
- <span class="build-light-text">
- {{ __('Merge Request:') }}
- </span>
- <a :href="job.merge_request.path">
- !{{ job.merge_request.iid }}
- </a>
- </p>
-
- <detail-row
- v-if="job.duration"
- :value="duration"
- class="js-job-duration"
- title="Duration"
- />
- <detail-row
- v-if="job.finished_at"
- :value="timeFormated(job.finished_at)"
- class="js-job-finished"
- title="Finished"
- />
- <detail-row
- v-if="job.erased_at"
- :value="timeFormated(job.erased_at)"
- class="js-job-erased"
- title="Erased"
- />
- <detail-row
- v-if="job.queued"
- :value="queued"
- class="js-job-queued"
- title="Queued"
- />
- <detail-row
- v-if="hasTimeout"
- :help-url="runnerHelpUrl"
- :value="timeout"
- class="js-job-timeout"
- title="Timeout"
- />
- <detail-row
- v-if="job.runner"
- :value="runnerId"
- class="js-job-runner"
- title="Runner"
- />
- <detail-row
- v-if="job.coverage"
- :value="coverage"
- class="js-job-coverage"
- title="Coverage"
- />
- <p
- v-if="job.tags.length"
- class="build-detail-row js-job-tags"
- >
- <span class="build-light-text">
- {{ __('Tags:') }}
- </span>
- <span
- v-for="(tag, i) in job.tags"
- :key="i"
- class="label label-primary">
- {{ tag }}
- </span>
- </p>
-
- <div
- v-if="job.cancel_path"
- class="btn-group prepend-top-5"
- role="group">
- <a
- :href="job.cancel_path"
- class="js-cancel-job btn btn-sm btn-default"
- data-method="post"
- rel="nofollow"
- >
- {{ __('Cancel') }}
- </a>
- </div>
- </div>
- <artifacts-block
- v-if="hasArtifact"
- :artifact="job.artifact"
- />
- <trigger-block
- v-if="hasTriggers"
- :trigger="job.trigger"
- />
- <commit-block
- :is-last-block="hasStages"
- :commit="commit"
- :merge-request="job.merge_request"
- />
+ </div>
- <stages-dropdown
- :stages="stages"
- :pipeline="job.pipeline"
- :selected-stage="selectedStage"
- @requestSidebarStageDropdown="fetchJobsForStage"
- />
+ <artifacts-block
+ v-if="hasArtifact"
+ :artifact="job.artifact"
+ />
+ <trigger-block
+ v-if="hasTriggers"
+ :trigger="job.trigger"
+ />
+ <commit-block
+ :is-last-block="hasStages"
+ :commit="commit"
+ :merge-request="job.merge_request"
+ />
- </template>
- <gl-loading-icon
- v-else
- :size="2"
- class="prepend-top-10"
+ <stages-dropdown
+ :stages="stages"
+ :pipeline="job.pipeline"
+ :selected-stage="selectedStage"
+ @requestSidebarStageDropdown="fetchJobsForStage"
/>
</div>
<jobs-container
- v-if="!isLoading && jobs.length"
+ v-if="jobs.length"
:jobs="jobs"
:job-id="job.id"
/>
diff --git a/app/assets/javascripts/jobs/components/stuck_block.vue b/app/assets/javascripts/jobs/components/stuck_block.vue
index a60643b2c65..1d5789b175a 100644
--- a/app/assets/javascripts/jobs/components/stuck_block.vue
+++ b/app/assets/javascripts/jobs/components/stuck_block.vue
@@ -23,14 +23,7 @@ export default {
<template>
<div class="bs-callout bs-callout-warning">
<p
- v-if="hasNoRunnersForProject"
- class="js-stuck-no-runners append-bottom-0"
- >
- {{ s__(`Job|This job is stuck, because the project
- doesn't have any runners online assigned to it.`) }}
- </p>
- <p
- v-else-if="tags.length"
+ v-if="tags.length"
class="js-stuck-with-tags append-bottom-0"
>
{{ s__(`This job is stuck, because you don't have
@@ -44,6 +37,13 @@ export default {
</span>
</p>
<p
+ v-else-if="hasNoRunnersForProject"
+ class="js-stuck-no-runners append-bottom-0"
+ >
+ {{ s__(`Job|This job is stuck, because the project
+ doesn't have any runners online assigned to it.`) }}
+ </p>
+ <p
v-else
class="js-stuck-no-active-runner append-bottom-0"
>
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
new file mode 100644
index 00000000000..ccd096a1da5
--- /dev/null
+++ b/app/assets/javascripts/jobs/index.js
@@ -0,0 +1,26 @@
+import Vue from 'vue';
+import JobApp from './components/job_app.vue';
+
+export default () => {
+ const element = document.getElementById('js-job-vue-app');
+
+ return new Vue({
+ el: element,
+ components: {
+ JobApp,
+ },
+ render(createElement) {
+ return createElement('job-app', {
+ props: {
+ runnerHelpUrl: element.dataset.runnerHelpUrl,
+ runnerSettingsUrl: element.dataset.runnerSettingsUrl,
+ endpoint: element.dataset.endpoint,
+ pagePath: element.dataset.buildOptionsPagePath,
+ logState: element.dataset.buildOptionsLogState,
+ buildStatus: element.dataset.buildOptionsBuildStatus,
+ },
+ });
+ },
+ });
+};
+
diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js
deleted file mode 100644
index 15cd79b1c50..00000000000
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import _ from 'underscore';
-import { mapState, mapActions } from 'vuex';
-import Vue from 'vue';
-import Job from '../job';
-import JobApp from './components/job_app.vue';
-import Sidebar from './components/sidebar.vue';
-import createStore from './store';
-
-export default () => {
- const { dataset } = document.getElementById('js-job-details-vue');
-
-
-
- const store = createStore();
- store.dispatch('setJobEndpoint', dataset.endpoint);
-
- store.dispatch('fetchJob');
-
- // Header
- // eslint-disable-next-line no-new
- new Vue({
- el: '#js-build-header-vue',
- components: {
- JobApp,
- },
- store,
- computed: {
- ...mapState(['job', 'isLoading']),
- },
- render(createElement) {
- return createElement('job-app', {
- props: {
- isLoading: this.isLoading,
- job: this.job,
- runnerSettingsUrl: dataset.runnerSettingsUrl,
- },
- });
- },
- });
-
- // Sidebar information block
- const detailsBlockElement = document.getElementById('js-details-block-vue');
- const detailsBlockDataset = detailsBlockElement.dataset;
- // eslint-disable-next-line
- new Vue({
- el: detailsBlockElement,
- components: {
- Sidebar,
- },
- computed: {
- ...mapState(['job']),
- },
- watch: {
- job(newVal, oldVal) {
- if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) {
- this.fetchStages();
- }
- },
- },
- methods: {
- ...mapActions(['fetchStages']),
- },
- store,
- render(createElement) {
- return createElement('sidebar', {
- props: {
- runnerHelpUrl: dataset.runnerHelpUrl,
- terminalPath: detailsBlockDataset.terminalPath,
- },
- });
- },
- });
-
- // 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 d0040161dc3..54ed217572a 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -1,17 +1,32 @@
import Visibility from 'visibilityjs';
import * as types from './mutation_types';
-import axios from '../../lib/utils/axios_utils';
-import Poll from '../../lib/utils/poll';
-import { setCiStatusFavicon } from '../../lib/utils/common_utils';
-import flash from '../../flash';
-import { __ } from '../../locale';
+import axios from '~/lib/utils/axios_utils';
+import Poll from '~/lib/utils/poll';
+import { setFaviconOverlay, resetFavicon } from '~/lib/utils/common_utils';
+import flash from '~/flash';
+import { __ } from '~/locale';
+import {
+ canScroll,
+ isScrolledToBottom,
+ isScrolledToTop,
+ isScrolledToMiddle,
+ scrollDown,
+ scrollUp,
+} from '~/lib/utils/scroll_utils';
export const setJobEndpoint = ({ commit }, endpoint) => commit(types.SET_JOB_ENDPOINT, endpoint);
-export const setTraceEndpoint = ({ commit }, endpoint) =>
- commit(types.SET_TRACE_ENDPOINT, endpoint);
-export const setStagesEndpoint = ({ commit }, endpoint) =>
- commit(types.SET_STAGES_ENDPOINT, endpoint);
-export const setJobsEndpoint = ({ commit }, endpoint) => commit(types.SET_JOBS_ENDPOINT, endpoint);
+export const setTraceOptions = ({ commit }, options) => commit(types.SET_TRACE_OPTIONS, options);
+
+export const hideSidebar = ({ commit }) => commit(types.HIDE_SIDEBAR);
+export const showSidebar = ({ commit }) => commit(types.SHOW_SIDEBAR);
+
+export const toggleSidebar = ({ dispatch, state }) => {
+ if (state.isSidebarOpen) {
+ dispatch('hideSidebar');
+ } else {
+ dispatch('showSidebar');
+ }
+};
let eTagPoll;
@@ -62,41 +77,84 @@ export const fetchJob = ({ state, dispatch }) => {
});
};
-export const receiveJobSuccess = ({ commit }, data) => {
+export const receiveJobSuccess = ({ commit }, data = {}) => {
commit(types.RECEIVE_JOB_SUCCESS, data);
+
+ if (data.favicon) {
+ setFaviconOverlay(data.favicon);
+ } else {
+ resetFavicon();
+ }
};
export const receiveJobError = ({ commit }) => {
commit(types.RECEIVE_JOB_ERROR);
flash(__('An error occurred while fetching the job.'));
+ resetFavicon();
};
/**
* Job's Trace
*/
-export const scrollTop = ({ commit }) => {
- commit(types.SCROLL_TO_TOP);
- window.scrollTo({ top: 0 });
+export const scrollTop = ({ dispatch }) => {
+ scrollUp();
+ dispatch('toggleScrollButtons');
};
-export const scrollBottom = ({ commit }) => {
- commit(types.SCROLL_TO_BOTTOM);
- window.scrollTo({ top: document.height });
+export const scrollBottom = ({ dispatch }) => {
+ scrollDown();
+ dispatch('toggleScrollButtons');
+};
+
+/**
+ * Responsible for toggling the disabled state of the scroll buttons
+ */
+export const toggleScrollButtons = ({ dispatch }) => {
+ if (canScroll()) {
+ if (isScrolledToMiddle()) {
+ dispatch('enableScrollTop');
+ dispatch('enableScrollBottom');
+ } else if (isScrolledToTop()) {
+ dispatch('disableScrollTop');
+ dispatch('enableScrollBottom');
+ } else if (isScrolledToBottom()) {
+ dispatch('disableScrollBottom');
+ dispatch('enableScrollTop');
+ }
+ } else {
+ dispatch('disableScrollBottom');
+ dispatch('disableScrollTop');
+ }
+};
+
+export const disableScrollBottom = ({ commit }) => commit(types.DISABLE_SCROLL_BOTTOM);
+export const disableScrollTop = ({ commit }) => commit(types.DISABLE_SCROLL_TOP);
+export const enableScrollBottom = ({ commit }) => commit(types.ENABLE_SCROLL_BOTTOM);
+export const enableScrollTop = ({ commit }) => commit(types.ENABLE_SCROLL_TOP);
+
+/**
+ * While the automatic scroll down is active,
+ * we show the scroll down button with an animation
+ */
+export const toggleScrollAnimation = ({ commit }, toggle) =>
+ commit(types.TOGGLE_SCROLL_ANIMATION, toggle);
+
+/**
+ * Responsible to handle automatic scroll
+ */
+export const toggleScrollisInBottom = ({ commit }, toggle) => {
+ commit(types.TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_TRACE, toggle);
};
export const requestTrace = ({ commit }) => commit(types.REQUEST_TRACE);
let traceTimeout;
-export const fetchTrace = ({ dispatch, state }) => {
- dispatch('requestTrace');
-
+export const fetchTrace = ({ dispatch, state }) =>
axios
.get(`${state.traceEndpoint}/trace.json`, {
params: { state: state.traceState },
})
.then(({ data }) => {
- if (!state.fetchingStatusFavicon) {
- dispatch('fetchFavicon');
- }
+ dispatch('toggleScrollisInBottom', isScrolledToBottom());
dispatch('receiveTraceSuccess', data);
if (!data.complete) {
@@ -108,7 +166,7 @@ export const fetchTrace = ({ dispatch, state }) => {
}
})
.catch(() => dispatch('receiveTraceError'));
-};
+
export const stopPollingTrace = ({ commit }) => {
commit(types.STOP_POLLING_TRACE);
clearTimeout(traceTimeout);
@@ -120,17 +178,6 @@ export const receiveTraceError = ({ commit }) => {
flash(__('An error occurred while fetching the job log.'));
};
-export const fetchFavicon = ({ state, dispatch }) => {
- dispatch('requestStatusFavicon');
- setCiStatusFavicon(`${state.pagePath}/status.json`)
- .then(() => dispatch('receiveStatusFaviconSuccess'))
- .catch(() => dispatch('requestStatusFaviconError'));
-};
-export const requestStatusFavicon = ({ commit }) => commit(types.REQUEST_STATUS_FAVICON);
-export const receiveStatusFaviconSuccess = ({ commit }) =>
- commit(types.RECEIVE_STATUS_FAVICON_SUCCESS);
-export const requestStatusFaviconError = ({ commit }) => commit(types.RECEIVE_STATUS_FAVICON_ERROR);
-
/**
* Stages dropdown on sidebar
*/
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index 9f4f372e3d2..4de01f8e532 100644
--- a/app/assets/javascripts/jobs/store/getters.js
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -1,5 +1,6 @@
import _ from 'underscore';
import { __ } from '~/locale';
+import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
export const headerActions = state => {
if (state.job.new_issue_path) {
@@ -34,20 +35,16 @@ export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
* Used to check if it should render the job log or the empty state
* @returns {Boolean}
*/
-export const hasTrace = state => state.job.has_trace || state.job.status.group === 'running';
+export const hasTrace = state => state.job.has_trace || (!_.isEmpty(state.job.status) && state.job.status.group === 'running');
export const emptyStateIllustration = state =>
(state.job && state.job.status && state.job.status.illustration) || {};
-/**
- * When the job is pending and there are no available runners
- * we need to render the stuck block;
- *
- * @returns {Boolean}
- */
-export const isJobStuck = state =>
- state.job.status.group === 'pending' &&
- (!_.isEmpty(state.job.runners) && state.job.runners.available === false);
+export const emptyStateAction = state => (state.job && state.job.status && state.job.status.action) || {};
+
+export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceComplete;
+
+export const hasRunnersForProject = state => state.job.runners.available && !state.job.runners.online;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/jobs/store/mutation_types.js b/app/assets/javascripts/jobs/store/mutation_types.js
index e66e1d4f116..fd098f13e90 100644
--- a/app/assets/javascripts/jobs/store/mutation_types.js
+++ b/app/assets/javascripts/jobs/store/mutation_types.js
@@ -1,10 +1,19 @@
export const SET_JOB_ENDPOINT = 'SET_JOB_ENDPOINT';
-export const SET_TRACE_ENDPOINT = 'SET_TRACE_ENDPOINT';
-export const SET_STAGES_ENDPOINT = 'SET_STAGES_ENDPOINT';
-export const SET_JOBS_ENDPOINT = 'SET_JOBS_ENDPOINT';
+export const SET_TRACE_OPTIONS = 'SET_TRACE_OPTIONS';
+
+export const HIDE_SIDEBAR = 'HIDE_SIDEBAR';
+export const SHOW_SIDEBAR = 'SHOW_SIDEBAR';
export const SCROLL_TO_TOP = 'SCROLL_TO_TOP';
export const SCROLL_TO_BOTTOM = 'SCROLL_TO_BOTTOM';
+export const DISABLE_SCROLL_BOTTOM = 'DISABLE_SCROLL_BOTTOM';
+export const DISABLE_SCROLL_TOP = 'DISABLE_SCROLL_TOP';
+export const ENABLE_SCROLL_BOTTOM = 'ENABLE_SCROLL_BOTTOM';
+export const ENABLE_SCROLL_TOP = 'ENABLE_SCROLL_TOP';
+// TODO
+export const TOGGLE_SCROLL_ANIMATION = 'TOGGLE_SCROLL_ANIMATION';
+
+export const TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_TRACE = 'TOGGLE_IS_SCROLL_IN_BOTTOM';
export const REQUEST_JOB = 'REQUEST_JOB';
export const RECEIVE_JOB_SUCCESS = 'RECEIVE_JOB_SUCCESS';
@@ -15,10 +24,6 @@ export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE';
export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS';
export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR';
-export const REQUEST_STATUS_FAVICON = 'REQUEST_STATUS_FAVICON';
-export const RECEIVE_STATUS_FAVICON_SUCCESS = 'RECEIVE_STATUS_FAVICON_SUCCESS';
-export const RECEIVE_STATUS_FAVICON_ERROR = 'RECEIVE_STATUS_FAVICON_ERROR';
-
export const REQUEST_STAGES = 'REQUEST_STAGES';
export const RECEIVE_STAGES_SUCCESS = 'RECEIVE_STAGES_SUCCESS';
export const RECEIVE_STAGES_ERROR = 'RECEIVE_STAGES_ERROR';
diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js
index f00e06e1a6c..4195d787f12 100644
--- a/app/assets/javascripts/jobs/store/mutations.js
+++ b/app/assets/javascripts/jobs/store/mutations.js
@@ -4,14 +4,17 @@ export default {
[types.SET_JOB_ENDPOINT](state, endpoint) {
state.jobEndpoint = endpoint;
},
- [types.REQUEST_STATUS_FAVICON](state) {
- state.fetchingStatusFavicon = true;
+
+ [types.SET_TRACE_OPTIONS](state, options = {}) {
+ state.traceEndpoint = options.pagePath;
+ state.traceState = options.logState;
},
- [types.RECEIVE_STATUS_FAVICON_SUCCESS](state) {
- state.fetchingStatusFavicon = false;
+
+ [types.HIDE_SIDEBAR](state) {
+ state.isSidebarOpen = false;
},
- [types.RECEIVE_STATUS_FAVICON_ERROR](state) {
- state.fetchingStatusFavicon = false;
+ [types.SHOW_SIDEBAR](state) {
+ state.isSidebarOpen = true;
},
[types.RECEIVE_TRACE_SUCCESS](state, log) {
@@ -23,8 +26,12 @@ export default {
state.trace += log.html;
state.traceSize += log.size;
} else {
- state.trace = log.html;
- state.traceSize = log.size;
+ // When the job still does not have a trace
+ // the trace response will not have a defined
+ // html or size. We keep the old value otherwise these
+ // will be set to `undefined`
+ state.trace = log.html || state.trace;
+ state.traceSize = log.size || state.traceSize;
}
if (state.traceSize < log.total) {
@@ -33,25 +40,29 @@ export default {
state.isTraceSizeVisible = false;
}
- state.isTraceComplete = log.complete;
- state.hasTraceError = false;
+ state.isTraceComplete = log.complete || state.isTraceComplete;
},
+
+ /**
+ * Will remove loading animation
+ */
[types.STOP_POLLING_TRACE](state) {
state.isTraceComplete = true;
},
- // todo_fl: check this.
+
+ /**
+ * Will remove loading animation
+ */
[types.RECEIVE_TRACE_ERROR](state) {
- state.isLoadingTrace = false;
state.isTraceComplete = true;
- state.hasTraceError = true;
},
[types.REQUEST_JOB](state) {
state.isLoading = true;
},
[types.RECEIVE_JOB_SUCCESS](state, job) {
- state.isLoading = false;
state.hasError = false;
+ state.isLoading = false;
state.job = job;
/**
@@ -66,17 +77,28 @@ export default {
},
[types.RECEIVE_JOB_ERROR](state) {
state.isLoading = false;
- state.hasError = true;
state.job = {};
+ state.hasError = true;
},
- [types.SCROLL_TO_TOP](state) {
- state.isTraceScrolledToBottom = false;
- state.hasBeenScrolled = true;
+ [types.ENABLE_SCROLL_TOP](state) {
+ state.isScrollTopDisabled = false;
+ },
+ [types.DISABLE_SCROLL_TOP](state) {
+ state.isScrollTopDisabled = true;
+ },
+ [types.ENABLE_SCROLL_BOTTOM](state) {
+ state.isScrollBottomDisabled = false;
},
- [types.SCROLL_TO_BOTTOM](state) {
- state.isTraceScrolledToBottom = true;
- state.hasBeenScrolled = true;
+ [types.DISABLE_SCROLL_BOTTOM](state) {
+ state.isScrollBottomDisabled = true;
+ },
+ [types.TOGGLE_SCROLL_ANIMATION](state, toggle) {
+ state.isScrollingDown = toggle;
+ },
+
+ [types.TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_TRACE](state, toggle) {
+ state.isScrolledToBottomBeforeReceivingTrace = toggle;
},
[types.REQUEST_STAGES](state) {
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
index afbc959bb71..0eb269ca38f 100644
--- a/app/assets/javascripts/jobs/store/state.js
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -4,36 +4,29 @@ export default () => ({
jobEndpoint: null,
traceEndpoint: null,
- // dropdown options
- stagesEndpoint: null,
- // list of jobs on sidebard
- stageJobsEndpoint: null,
+ // sidebar
+ isSidebarOpen: true,
- // job log
isLoading: false,
hasError: false,
job: {},
- // trace
- isLoadingTrace: false,
- hasTraceError: false,
+ // scroll buttons state
+ isScrollBottomDisabled: true,
+ isScrollTopDisabled: true,
- trace: '',
-
- isTraceScrolledToBottom: false,
- hasBeenScrolled: false,
+ // Used to check if we should keep the automatic scroll
+ isScrolledToBottomBeforeReceivingTrace: true,
+ trace: '',
isTraceComplete: false,
- traceSize: 0, // todo_fl: needs to be converted into human readable format in components
+ traceSize: 0,
isTraceSizeVisible: false,
- fetchingStatusFavicon: false,
- // used as a query parameter
+ // used as a query parameter to fetch the trace
traceState: null,
- // used to check if we need to redirect the user - todo_fl: check if actually needed
- traceStatus: null,
- // sidebar dropdown
+ // sidebar dropdown & list of jobs
isLoadingStages: false,
isLoadingJobs: false,
selectedStage: __('More'),
diff --git a/app/assets/javascripts/jobs/svg/scroll_down.svg b/app/assets/javascripts/jobs/svg/scroll_down.svg
new file mode 100644
index 00000000000..1d22870ec09
--- /dev/null
+++ b/app/assets/javascripts/jobs/svg/scroll_down.svg
@@ -0,0 +1,5 @@
+<svg width="12" height="16" viewBox="0 0 12 16" xmlns="http://www.w3.org/2000/svg">
+ <path class="first-triangle" d="M1.048 14.155a.508.508 0 0 0-.32.105c-.091.07-.136.154-.136.25v.71c0 .095.045.178.135.249.09.07.197.105.321.105h10.043c.124 0 .23-.035.321-.105.09-.07.136-.154.136-.25v-.71c0-.095-.045-.178-.136-.249a.508.508 0 0 0-.32-.105"/>
+ <path class="second-triangle" d="M.687 8.027c-.09-.087-.122-.16-.093-.22.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 12.91a.458.458 0 0 1-.136.089h-.37a.626.626 0 0 1-.136-.09"/>
+ <path class="third-triangle" d="M.687 1.027C.597.94.565.867.594.807c.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 5.91A.458.458 0 0 1 6.257 6h-.37a.626.626 0 0 1-.136-.09"/>
+</svg>
diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js
index 2c995c5902f..062501d1d04 100644
--- a/app/assets/javascripts/label_manager.js
+++ b/app/assets/javascripts/label_manager.js
@@ -47,7 +47,10 @@ export default class LabelManager {
}
toggleEmptyState($label, $btn, action) {
- this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
+ this.emptyState.classList.toggle(
+ 'hidden',
+ !!this.prioritizedLabels[0].querySelector(':scope > li'),
+ );
}
toggleLabelPriority($label, action, persistState) {
@@ -80,16 +83,14 @@ export default class LabelManager {
return;
}
if (action === 'remove') {
- axios.delete(url)
- .catch(rollbackLabelPosition);
+ axios.delete(url).catch(rollbackLabelPosition);
// Restore empty message
if (!$from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
} else {
- this.savePrioritySort($label, action)
- .catch(rollbackLabelPosition);
+ this.savePrioritySort($label, action).catch(rollbackLabelPosition);
}
}
@@ -102,8 +103,7 @@ export default class LabelManager {
}
onPrioritySortUpdate() {
- this.savePrioritySort()
- .catch(() => flash(this.errorMessage));
+ this.savePrioritySort().catch(() => flash(this.errorMessage));
}
savePrioritySort() {
diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js
index d85ae851706..2bc09237495 100644
--- a/app/assets/javascripts/labels.js
+++ b/app/assets/javascripts/labels.js
@@ -22,7 +22,7 @@ export default class Labels {
updateColorPreview() {
const previewColor = $('input#label_color').val();
return $('div.label-color-preview').css('background-color', previewColor);
- // Updates the the preview color with the hex-color input
+ // Updates the the preview color with the hex-color input
}
// Updates the preview color with a click on a suggested color
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index e3177188772..b8c3c237eb3 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -5,7 +5,9 @@ import initFlyOutNav from './fly_out_nav';
function hideEndFade($scrollingTabs) {
$scrollingTabs.each(function scrollTabsLoop() {
const $this = $(this);
- $this.siblings('.fade-right').toggleClass('scrolling', Math.round($this.width()) < $this.prop('scrollWidth'));
+ $this
+ .siblings('.fade-right')
+ .toggleClass('scrolling', Math.round($this.width()) < $this.prop('scrollWidth'));
});
}
@@ -15,38 +17,42 @@ export default function initLayoutNav() {
initFlyOutNav();
- $(document).on('init.scrolling-tabs', () => {
- const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
- $scrollingTabs.addClass('is-initialized');
+ $(document)
+ .on('init.scrolling-tabs', () => {
+ const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
+ $scrollingTabs.addClass('is-initialized');
- $(window).on('resize.nav', () => {
- hideEndFade($scrollingTabs);
- }).trigger('resize.nav');
+ $(window)
+ .on('resize.nav', () => {
+ hideEndFade($scrollingTabs);
+ })
+ .trigger('resize.nav');
- $scrollingTabs.on('scroll', function tabsScrollEvent() {
- const $this = $(this);
- const currentPosition = $this.scrollLeft();
- const maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
+ $scrollingTabs.on('scroll', function tabsScrollEvent() {
+ const $this = $(this);
+ const currentPosition = $this.scrollLeft();
+ const maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
- $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
- $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
- });
+ $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
+ $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
+ });
- $scrollingTabs.each(function scrollTabsEachLoop() {
- const $this = $(this);
- const scrollingTabWidth = $this.width();
- const $active = $this.find('.active');
- const activeWidth = $active.width();
+ $scrollingTabs.each(function scrollTabsEachLoop() {
+ const $this = $(this);
+ const scrollingTabWidth = $this.width();
+ const $active = $this.find('.active');
+ const activeWidth = $active.width();
- if ($active.length) {
- const offset = $active.offset().left + activeWidth;
+ if ($active.length) {
+ const offset = $active.offset().left + activeWidth;
- if (offset > scrollingTabWidth - 30) {
- const scrollLeft = (offset - (scrollingTabWidth / 2)) - (activeWidth / 2);
+ if (offset > scrollingTabWidth - 30) {
+ const scrollLeft = offset - scrollingTabWidth / 2 - activeWidth / 2;
- $this.scrollLeft(scrollLeft);
+ $this.scrollLeft(scrollLeft);
+ }
}
- }
- });
- }).trigger('init.scrolling-tabs');
+ });
+ })
+ .trigger('init.scrolling-tabs');
}
diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js
deleted file mode 100644
index 19e4085dbbb..00000000000
--- a/app/assets/javascripts/lib/utils/datefix.js
+++ /dev/null
@@ -1,28 +0,0 @@
-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 => {
- const parts = dateString.split('-');
- const year = parseInt(parts[0], 10);
- const month = parseInt(parts[1] - 1, 10);
- const day = parseInt(parts[2], 10);
-
- return new Date(year, month, day);
-};
-
-/**
- * Used `onSelect` method in pickaday
- * @param {Date} date UTC format
- * @return {String} Date formated in yyyy-mm-dd
- */
-export const pikadayToString = date => {
- const day = pad(date.getDate());
- const month = pad(date.getMonth() + 1);
- const year = date.getFullYear();
-
- return `${year}-${month}-${day}`;
-};
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 833dbefd3dc..46740308f17 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import _ from 'underscore';
import timeago from 'timeago.js';
import dateFormat from 'dateformat';
import { pluralize } from './text_utility';
@@ -46,6 +47,8 @@ const getMonthNames = abbreviated => {
];
};
+export const pad = (val, len = 2) => `0${val}`.slice(-len);
+
/**
* Given a date object returns the day of the week in English
* @param {date} date
@@ -74,10 +77,10 @@ let timeagoInstance;
/**
* Sets a timeago Instance
*/
-export function getTimeago() {
+export const getTimeago = () => {
if (!timeagoInstance) {
- const localeRemaining = function getLocaleRemaining(number, index) {
- return [
+ const localeRemaining = (number, index) =>
+ [
[s__('Timeago|just now'), s__('Timeago|right now')],
[s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')],
[s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')],
@@ -93,9 +96,9 @@ export function getTimeago() {
[s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')],
[s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')],
][index];
- };
- const locale = function getLocale(number, index) {
- return [
+
+ const locale = (number, index) =>
+ [
[s__('Timeago|just now'), s__('Timeago|right now')],
[s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')],
[s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')],
@@ -111,7 +114,6 @@ export function getTimeago() {
[s__('Timeago|1 year ago'), s__('Timeago|in 1 year')],
[s__('Timeago|%s years ago'), s__('Timeago|in %s years')],
][index];
- };
timeago.register(timeagoLanguageCode, locale);
timeago.register(`${timeagoLanguageCode}-remaining`, localeRemaining);
@@ -119,7 +121,7 @@ export function getTimeago() {
}
return timeagoInstance;
-}
+};
/**
* For the given element, renders a timeago instance.
@@ -184,7 +186,7 @@ export const getDayDifference = (a, b) => {
* @param {Number} seconds
* @return {String}
*/
-export function timeIntervalInWords(intervalInSeconds) {
+export const timeIntervalInWords = intervalInSeconds => {
const secondsInteger = parseInt(intervalInSeconds, 10);
const minutes = Math.floor(secondsInteger / 60);
const seconds = secondsInteger - minutes * 60;
@@ -196,9 +198,9 @@ export function timeIntervalInWords(intervalInSeconds) {
text = `${seconds} ${pluralize('second', seconds)}`;
}
return text;
-}
+};
-export function dateInWords(date, abbreviated = false, hideYear = false) {
+export const dateInWords = (date, abbreviated = false, hideYear = false) => {
if (!date) return date;
const month = date.getMonth();
@@ -240,7 +242,7 @@ export function dateInWords(date, abbreviated = false, hideYear = false) {
}
return `${monthName} ${date.getDate()}, ${year}`;
-}
+};
/**
* Returns month name based on provided date.
@@ -391,3 +393,95 @@ export const formatTime = milliseconds => {
formattedTime += remainingSeconds;
return formattedTime;
};
+
+/**
+ * Formats dates in Pickaday
+ * @param {String} dateString Date in yyyy-mm-dd format
+ * @return {Date} UTC format
+ */
+export const parsePikadayDate = dateString => {
+ const parts = dateString.split('-');
+ const year = parseInt(parts[0], 10);
+ const month = parseInt(parts[1] - 1, 10);
+ const day = parseInt(parts[2], 10);
+
+ return new Date(year, month, day);
+};
+
+/**
+ * Used `onSelect` method in pickaday
+ * @param {Date} date UTC format
+ * @return {String} Date formated in yyyy-mm-dd
+ */
+export const pikadayToString = date => {
+ const day = pad(date.getDate());
+ const month = pad(date.getMonth() + 1);
+ const year = date.getFullYear();
+
+ return `${year}-${month}-${day}`;
+};
+
+/**
+ * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
+ * Seconds can be negative or positive, zero or non-zero. Can be configured for any day
+ * or week length.
+ */
+export const parseSeconds = (seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) => {
+ const DAYS_PER_WEEK = daysPerWeek;
+ const HOURS_PER_DAY = hoursPerDay;
+ const MINUTES_PER_HOUR = 60;
+ const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
+ const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR;
+
+ const timePeriodConstraints = {
+ weeks: MINUTES_PER_WEEK,
+ days: MINUTES_PER_DAY,
+ hours: MINUTES_PER_HOUR,
+ minutes: 1,
+ };
+
+ let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR);
+
+ return _.mapObject(timePeriodConstraints, minutesPerPeriod => {
+ const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
+
+ unorderedMinutes -= periodCount * minutesPerPeriod;
+
+ return periodCount;
+ });
+};
+
+/**
+ * Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it
+ * (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
+ */
+export const stringifyTime = timeObject => {
+ const reducedTime = _.reduce(
+ timeObject,
+ (memo, unitValue, unitName) => {
+ const isNonZero = !!unitValue;
+ return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
+ },
+ '',
+ ).trim();
+ return reducedTime.length ? reducedTime : '0m';
+};
+
+/**
+ * Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns
+ * the first non-zero unit/value pair.
+ */
+export const abbreviateTime = timeStr =>
+ timeStr.split(' ').filter(unitStr => unitStr.charAt(0) !== '0')[0];
+
+/**
+ * Calculates the milliseconds between now and a given date string.
+ * The result cannot become negative.
+ *
+ * @param endDate date string that the time difference is calculated for
+ * @return {number} number of milliseconds remaining until the given date
+ */
+export const calculateRemainingMilliseconds = endDate => {
+ const remainingMilliseconds = new Date(endDate).getTime() - Date.now();
+ return Math.max(remainingMilliseconds, 0);
+};
diff --git a/app/assets/javascripts/lib/utils/pretty_time.js b/app/assets/javascripts/lib/utils/pretty_time.js
deleted file mode 100644
index d92b8a7179f..00000000000
--- a/app/assets/javascripts/lib/utils/pretty_time.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import _ from 'underscore';
-
-/*
- * TODO: Make these methods more configurable (e.g. stringifyTime condensed or
- * non-condensed, abbreviateTimelengths)
- * */
-
-/*
- * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
- * Seconds can be negative or positive, zero or non-zero. Can be configured for any day
- * or week length.
-*/
-
-export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) {
- const DAYS_PER_WEEK = daysPerWeek;
- const HOURS_PER_DAY = hoursPerDay;
- const MINUTES_PER_HOUR = 60;
- const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR;
- const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR;
-
- const timePeriodConstraints = {
- weeks: MINUTES_PER_WEEK,
- days: MINUTES_PER_DAY,
- hours: MINUTES_PER_HOUR,
- minutes: 1,
- };
-
- let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR);
-
- return _.mapObject(timePeriodConstraints, minutesPerPeriod => {
- const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
-
- unorderedMinutes -= periodCount * minutesPerPeriod;
-
- return periodCount;
- });
-}
-
-/*
-* Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it
-* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
-*/
-
-export function stringifyTime(timeObject) {
- const reducedTime = _.reduce(
- timeObject,
- (memo, unitValue, unitName) => {
- const isNonZero = !!unitValue;
- return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
- },
- '',
- ).trim();
- return reducedTime.length ? reducedTime : '0m';
-}
-
-/*
-* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns
-* the first non-zero unit/value pair.
-*/
-
-export function abbreviateTime(timeStr) {
- return timeStr.split(' ').filter(unitStr => unitStr.charAt(0) !== '0')[0];
-}
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index d58fd63bb33..4db63c841a9 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -70,7 +70,7 @@ LineHighlighter.prototype.highlightHash = function(newHash) {
const scrollOptions = {
// Scroll to the first highlighted line on initial load
// Offset -50 for the sticky top bar, and another -100 for some context
- offset: -150
+ offset: -150,
};
if (this.options.scrollFileHolder) {
$(this.options.fileHolderSelector).scrollTo(lineSelector, scrollOptions);
@@ -85,7 +85,9 @@ LineHighlighter.prototype.clickHandler = function(event) {
var current, lineNumber, range;
event.preventDefault();
this.clearHighlight();
- lineNumber = $(event.target).closest('a').data('lineNumber');
+ lineNumber = $(event.target)
+ .closest('a')
+ .data('lineNumber');
current = this.hashToRange(this._hash);
if (!(current[0] && event.shiftKey)) {
// If there's no current selection, or there is but Shift wasn't held,
@@ -104,7 +106,7 @@ LineHighlighter.prototype.clickHandler = function(event) {
};
LineHighlighter.prototype.clearHighlight = function() {
- return $("." + this.highlightLineClass).removeClass(this.highlightLineClass);
+ return $('.' + this.highlightLineClass).removeClass(this.highlightLineClass);
};
// Convert a URL hash String into line numbers
@@ -135,7 +137,7 @@ LineHighlighter.prototype.hashToRange = function(hash) {
//
// lineNumber - Line number to highlight
LineHighlighter.prototype.highlightLine = function(lineNumber) {
- return $("#LC" + lineNumber).addClass(this.highlightLineClass);
+ return $('#LC' + lineNumber).addClass(this.highlightLineClass);
};
// Highlight all lines within a range
@@ -160,9 +162,9 @@ LineHighlighter.prototype.highlightRange = function(range) {
LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
var hash;
if (lastLineNumber) {
- hash = "#L" + firstLineNumber + "-" + lastLineNumber;
+ hash = '#L' + firstLineNumber + '-' + lastLineNumber;
} else {
- hash = "#L" + firstLineNumber;
+ hash = '#L' + firstLineNumber;
}
this._hash = hash;
return this.__setLocationHash__(hash);
@@ -172,11 +174,15 @@ LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
//
// This method is stubbed in tests.
LineHighlighter.prototype.__setLocationHash__ = function(value) {
- return window.history.pushState({
- url: value
- // We're using pushState instead of assigning location.hash directly to
- // prevent the page from scrolling on the hashchange event
- }, document.title, value);
+ return window.history.pushState(
+ {
+ url: value,
+ // We're using pushState instead of assigning location.hash directly to
+ // prevent the page from scrolling on the hashchange event
+ },
+ document.title,
+ value,
+ );
};
export default LineHighlighter;
diff --git a/app/assets/javascripts/locale/sprintf.js b/app/assets/javascripts/locale/sprintf.js
index 599104dcfa0..5246c49842e 100644
--- a/app/assets/javascripts/locale/sprintf.js
+++ b/app/assets/javascripts/locale/sprintf.js
@@ -15,7 +15,7 @@ export default (input, parameters, escapeParameters = true) => {
let output = input;
if (parameters) {
- Object.keys(parameters).forEach((parameterName) => {
+ Object.keys(parameters).forEach(parameterName => {
const parameterValue = parameters[parameterName];
const escapedParameterValue = escapeParameters ? _.escape(parameterValue) : parameterValue;
output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue);
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index d27922a2099..0beedcacf33 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import Pikaday from 'pikaday';
-import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
+import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility';
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
@@ -9,7 +9,9 @@ import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
//
export default function memberExpirationDate(selector = '.js-access-expiration-date') {
function toggleClearInput() {
- $(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== '');
+ $(this)
+ .closest('.clearable-input')
+ .toggleClass('has-value', $(this).val() !== '');
}
const inputs = $(selector);
@@ -40,7 +42,9 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
inputs.next('.js-clear-input').on('click', function clicked(event) {
event.preventDefault();
- const input = $(this).closest('.clearable-input').find(selector);
+ const input = $(this)
+ .closest('.clearable-input')
+ .find(selector);
const calendar = input.data('pikaday');
calendar.setDate(null);
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 9b6d7d1772f..0deae478deb 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -16,26 +16,29 @@ function MergeRequest(opts) {
this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request');
- this.$('.show-all-commits').on('click', (function(_this) {
- return function() {
- return _this.showAllCommits();
- };
- })(this));
+ this.$('.show-all-commits').on(
+ 'click',
+ (function(_this) {
+ return function() {
+ return _this.showAllCommits();
+ };
+ })(this),
+ );
this.initTabs();
this.initMRBtnListeners();
this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
- if ($("a.btn-close").length) {
+ if ($('a.btn-close').length) {
this.taskList = new TaskList({
dataType: 'merge_request',
fieldName: 'description',
selector: '.detail-page-description',
- onSuccess: (result) => {
+ onSuccess: result => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
- }
+ },
});
}
}
@@ -84,7 +87,7 @@ MergeRequest.prototype.initMRBtnListeners = function() {
MergeRequest.prototype.submitNoteForm = function(form, $button) {
var noteText;
- noteText = form.find("textarea.js-note-text").val();
+ noteText = form.find('textarea.js-note-text').val();
if (noteText.trim().length > 0) {
form.submit();
$button.data('submitted', true);
@@ -122,7 +125,7 @@ MergeRequest.setStatusBoxToMerged = function() {
MergeRequest.decreaseCounter = function(by = 1) {
const $el = $('.js-merge-counter');
- const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
+ const count = Math.max(parseInt($el.text().replace(/[^\d]/, ''), 10) - by, 0);
$el.text(addDelimiter(count));
};
diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js
index 03f3bb42193..2950c2299ab 100644
--- a/app/assets/javascripts/merge_request_tabs.js
+++ b/app/assets/javascripts/merge_request_tabs.js
@@ -419,7 +419,7 @@ export default class MergeRequestTabs {
if (this.diffViewType() === 'parallel' || removeLimited) {
$wrapper.removeClass('container-limited');
} else {
- $wrapper.addClass('container-limited');
+ $wrapper.toggleClass('container-limited', this.fixedLayoutPref);
}
}
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index 6da04020881..f211632cf24 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -15,7 +15,7 @@ export default class Milestone {
}
bindTabsSwitching() {
- return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
+ return $('a[data-toggle="tab"]').on('show.bs.tab', e => {
const $target = $(e.target);
window.location.hash = $target.attr('href');
@@ -36,7 +36,8 @@ export default class Milestone {
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) {
- axios.get(endpoint)
+ axios
+ .get(endpoint)
.then(({ data }) => {
$(tabElId).html(data.html);
$target.addClass('is-loaded');
@@ -46,23 +47,28 @@ export default class Milestone {
}
static initDeprecationMessage() {
- const deprecationMesssageContainer = document.querySelector('.js-milestone-deprecation-message');
+ const deprecationMesssageContainer = document.querySelector(
+ '.js-milestone-deprecation-message',
+ );
if (!deprecationMesssageContainer) return;
- const deprecationMessage = deprecationMesssageContainer.querySelector('.js-milestone-deprecation-message-template').innerHTML;
+ const deprecationMessage = deprecationMesssageContainer.querySelector(
+ '.js-milestone-deprecation-message-template',
+ ).innerHTML;
const $popover = $('.js-popover-link', deprecationMesssageContainer);
const hideOnScroll = togglePopover.bind($popover, false);
- $popover.popover({
- content: deprecationMessage,
- html: true,
- placement: 'bottom',
- })
- .on('mouseenter', mouseenter)
- .on('mouseleave', debouncedMouseleave())
- .on('show.bs.popover', () => {
- window.addEventListener('scroll', hideOnScroll, { once: true });
- });
+ $popover
+ .popover({
+ content: deprecationMessage,
+ html: true,
+ placement: 'bottom',
+ })
+ .on('mouseenter', mouseenter)
+ .on('mouseleave', debouncedMouseleave())
+ .on('show.bs.popover', () => {
+ window.addEventListener('scroll', hideOnScroll, { once: true });
+ });
}
}
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
index f8257b6abab..81ab9d8be4b 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
@@ -46,7 +46,7 @@ export default class MiniPipelineGraph {
$(document).on(
'click',
`${this.container} .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item`,
- (e) => {
+ e => {
e.stopPropagation();
},
);
@@ -82,7 +82,8 @@ export default class MiniPipelineGraph {
this.renderBuildsList(button, '');
this.toggleLoading(button);
- axios.get(endpoint)
+ axios
+ .get(endpoint)
.then(({ data }) => {
this.toggleLoading(button);
this.renderBuildsList(button, data.html);
@@ -90,7 +91,11 @@ export default class MiniPipelineGraph {
})
.catch(() => {
this.toggleLoading(button);
- if ($(button).parent().hasClass('open')) {
+ if (
+ $(button)
+ .parent()
+ .hasClass('open')
+ ) {
$(button).dropdown('toggle');
}
flash('An error occurred while fetching the builds.', 'alert');
@@ -104,8 +109,8 @@ export default class MiniPipelineGraph {
* @return {type}
*/
toggleLoading(stageContainer) {
- stageContainer.parentElement.querySelector(
- `${this.dropdownListSelector} .js-builds-dropdown-loading`,
- ).classList.toggle('hidden');
+ stageContainer.parentElement
+ .querySelector(`${this.dropdownListSelector} .js-builds-dropdown-loading`)
+ .classList.toggle('hidden');
}
}
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index ed5c8b15945..5c6e2e09e46 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -121,6 +121,7 @@ export default {
draw() {
const breakpointSize = bp.getBreakpointSize();
const query = this.graphData.queries[0];
+ const svgWidth = this.$refs.baseSvg.getBoundingClientRect().width;
this.margin = measurements.large.margin;
if (this.smallGraph || breakpointSize === 'xs' || breakpointSize === 'sm') {
this.graphHeight = 300;
@@ -130,13 +131,13 @@ export default {
this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.graphData.y_label || 'Values';
this.legendTitle = query.label || 'Average';
- this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right;
+ this.graphWidth = svgWidth - this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
this.baseGraphHeight = this.graphHeight - 50;
this.baseGraphWidth = this.graphWidth;
// pixel offsets inside the svg and outside are not 1:1
- this.realPixelRatio = this.$refs.baseSvg.clientWidth / this.baseGraphWidth;
+ this.realPixelRatio = svgWidth / this.baseGraphWidth;
this.renderAxesPaths();
this.formatDeployments();
diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js
index 8aabb840847..1c98683c597 100644
--- a/app/assets/javascripts/mr_notes/index.js
+++ b/app/assets/javascripts/mr_notes/index.js
@@ -4,6 +4,7 @@ import { mapActions, mapState, mapGetters } from 'vuex';
import initDiffsApp from '../diffs';
import notesApp from '../notes/components/notes_app.vue';
import discussionCounter from '../notes/components/discussion_counter.vue';
+import initDiscussionFilters from '../notes/discussion_filters';
import store from './stores';
import MergeRequest from '../merge_request';
@@ -88,5 +89,6 @@ export default function initMrNotes() {
},
});
+ initDiscussionFilters(store);
initDiffsApp(store);
}
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index ec4c0910e92..cba6759ebf5 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -14,14 +14,14 @@ export default class NamespaceSelect {
selectable: true,
filterRemote: true,
search: {
- fields: ['path']
+ fields: ['path'],
},
fieldName: fieldName,
toggleLabel: function(selected) {
if (selected.id == null) {
return selected.text;
} else {
- return selected.kind + ": " + selected.full_path;
+ return selected.kind + ': ' + selected.full_path;
}
},
data: function(term, dataCallback) {
@@ -29,7 +29,7 @@ export default class NamespaceSelect {
if (isFilter) {
const anyNamespace = {
text: 'Any namespace',
- id: null
+ id: null,
};
namespaces.unshift(anyNamespace);
namespaces.splice(1, 0, 'divider');
@@ -41,7 +41,7 @@ export default class NamespaceSelect {
if (namespace.id == null) {
return namespace.text;
} else {
- return namespace.kind + ": " + namespace.full_path;
+ return namespace.kind + ': ' + namespace.full_path;
}
},
renderRow: this.renderRow,
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index ad7136adb8c..d1fa9f5e2a2 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -20,7 +20,7 @@ export default (function() {
this.mtime = 0;
this.mspace = 0;
this.parents = {};
- this.colors = ["#000"];
+ this.colors = ['#000'];
this.offsetX = 150;
this.offsetY = 20;
this.unitTime = 30;
@@ -30,9 +30,10 @@ export default (function() {
}
BranchGraph.prototype.load = function() {
- axios.get(this.options.url)
+ axios
+ .get(this.options.url)
.then(({ data }) => {
- $(".loading", this.element).hide();
+ $('.loading', this.element).hide();
this.prepareData(data.days, data.commits);
this.buildGraph();
})
@@ -71,17 +72,19 @@ export default (function() {
c = ref[j];
this.mtime = Math.max(this.mtime, c.time);
this.mspace = Math.max(this.mspace, c.space);
- results.push((function() {
- var l, len1, ref1, results1;
- ref1 = c.parents;
- results1 = [];
- for (l = 0, len1 = ref1.length; l < len1; l += 1) {
- p = ref1[l];
- this.parents[p[0]] = true;
- results1.push(this.mspace = Math.max(this.mspace, p[1]));
- }
- return results1;
- }).call(this));
+ results.push(
+ function() {
+ var l, len1, ref1, results1;
+ ref1 = c.parents;
+ results1 = [];
+ for (l = 0, len1 = ref1.length; l < len1; l += 1) {
+ p = ref1[l];
+ this.parents[p[0]] = true;
+ results1.push((this.mspace = Math.max(this.mspace, p[1])));
+ }
+ return results1;
+ }.call(this),
+ );
}
return results;
};
@@ -91,11 +94,11 @@ export default (function() {
k = 0;
results = [];
while (k < this.mspace) {
- this.colors.push(Raphael.getColor(.8));
+ this.colors.push(Raphael.getColor(0.8));
// Skipping a few colors in the spectrum to get more contrast between colors
Raphael.getColor();
Raphael.getColor();
- results.push(k += 1);
+ results.push((k += 1));
}
return results;
};
@@ -104,12 +107,12 @@ export default (function() {
var cuday, cumonth, day, j, len, mm, ref;
const { r } = this;
cuday = 0;
- cumonth = "";
+ cumonth = '';
r.rect(0, 0, 40, this.barHeight).attr({
- fill: "#222"
+ fill: '#222',
});
r.rect(40, 0, 30, this.barHeight).attr({
- fill: "#444"
+ fill: '#444',
});
ref = this.days;
@@ -118,16 +121,16 @@ export default (function() {
if (cuday !== day[0] || cumonth !== day[1]) {
// Dates
r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
- font: "12px Monaco, monospace",
- fill: "#BBB"
+ font: '12px Monaco, monospace',
+ fill: '#BBB',
});
[cuday] = day;
}
if (cumonth !== day[1]) {
// Months
r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
- font: "12px Monaco, monospace",
- fill: "#EEE"
+ font: '12px Monaco, monospace',
+ fill: '#EEE',
});
// eslint-disable-next-line prefer-destructuring
@@ -173,11 +176,13 @@ export default (function() {
BranchGraph.prototype.bindEvents = function() {
const { element } = this;
- return $(element).scroll((function(_this) {
- return function(event) {
- return _this.renderPartialGraph();
- };
- })(this));
+ return $(element).scroll(
+ (function(_this) {
+ return function(event) {
+ return _this.renderPartialGraph();
+ };
+ })(this),
+ );
};
BranchGraph.prototype.scrollDown = function() {
@@ -219,46 +224,53 @@ export default (function() {
shortrefs = commit.refs;
// Truncate if longer than 15 chars
if (shortrefs.length > 17) {
- shortrefs = shortrefs.substr(0, 15) + "…";
+ shortrefs = shortrefs.substr(0, 15) + '…';
}
text = r.text(x + 4, y, shortrefs).attr({
- "text-anchor": "start",
- font: "10px Monaco, monospace",
- fill: "#FFF",
- title: commit.refs
+ 'text-anchor': 'start',
+ font: '10px Monaco, monospace',
+ fill: '#FFF',
+ title: commit.refs,
});
textbox = text.getBBox();
// Create rectangle based on the size of the textbox
rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
- fill: "#000",
- "fill-opacity": .5,
- stroke: "none"
+ fill: '#000',
+ 'fill-opacity': 0.5,
+ stroke: 'none',
});
- triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({
- fill: "#000",
- "fill-opacity": .5,
- stroke: "none"
+ triangle = r.path(['M', x - 5, y, 'L', x - 15, y - 4, 'L', x - 15, y + 4, 'Z']).attr({
+ fill: '#000',
+ 'fill-opacity': 0.5,
+ stroke: 'none',
});
label = r.set(rect, text);
- label.transform(["t", -rect.getBBox().width - 15, 0]);
+ label.transform(['t', -rect.getBBox().width - 15, 0]);
// Set text to front
return text.toFront();
};
BranchGraph.prototype.appendAnchor = function(x, y, commit) {
const { r, top, options } = this;
- const anchor = r.circle(x, y, 10).attr({
- fill: "#000",
- opacity: 0,
- cursor: "pointer"
- }).click(function() {
- return window.open(options.commit_url.replace("%s", commit.id), "_blank");
- }).hover(function() {
- this.tooltip = r.commitTooltip(x + 5, y, commit);
- return top.push(this.tooltip.insertBefore(this));
- }, function() {
- return this.tooltip && this.tooltip.remove() && delete this.tooltip;
- });
+ const anchor = r
+ .circle(x, y, 10)
+ .attr({
+ fill: '#000',
+ opacity: 0,
+ cursor: 'pointer',
+ })
+ .click(function() {
+ return window.open(options.commit_url.replace('%s', commit.id), '_blank');
+ })
+ .hover(
+ function() {
+ this.tooltip = r.commitTooltip(x + 5, y, commit);
+ return top.push(this.tooltip.insertBefore(this));
+ },
+ function() {
+ return this.tooltip && this.tooltip.remove() && delete this.tooltip;
+ },
+ );
return top.push(anchor);
};
@@ -266,7 +278,7 @@ export default (function() {
const { r } = this;
r.circle(x, y, 3).attr({
fill: this.colors[commit.space],
- stroke: "none"
+ stroke: 'none',
});
const avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10;
@@ -274,13 +286,15 @@ export default (function() {
r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({
stroke: this.colors[commit.space],
- "stroke-width": 2
+ 'stroke-width': 2,
});
r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20);
- return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({
- "text-anchor": "start",
- font: "14px Monaco, monospace"
- });
+ return r
+ .text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split('\n')[0])
+ .attr({
+ 'text-anchor': 'start',
+ font: '14px Monaco, monospace',
+ });
};
BranchGraph.prototype.drawLines = function(x, y, commit) {
@@ -304,30 +318,32 @@ export default (function() {
// Build line shape
if (parent[1] === commit.space) {
offset = [0, 5];
- arrow = "l-2,5,4,0,-2,-5,0,5";
+ arrow = 'l-2,5,4,0,-2,-5,0,5';
} else if (parent[1] < commit.space) {
offset = [3, 3];
- arrow = "l5,0,-2,4,-3,-4,4,2";
+ arrow = 'l5,0,-2,4,-3,-4,4,2';
} else {
offset = [-3, 3];
- arrow = "l-5,0,2,4,3,-4,-4,2";
+ arrow = 'l-5,0,2,4,3,-4,-4,2';
}
// Start point
- route = ["M", x + offset[0], y + offset[1]];
+ route = ['M', x + offset[0], y + offset[1]];
// Add arrow if not first parent
if (i > 0) {
route.push(arrow);
}
// Circumvent if overlap
if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
- route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5);
+ route.push('L', parentX2, y + 10, 'L', parentX2, parentY - 5);
}
// End point
- route.push("L", parentX1, parentY);
- results.push(r.path(route).attr({
- stroke: color,
- "stroke-width": 2
- }));
+ route.push('L', parentX1, parentY);
+ results.push(
+ r.path(route).attr({
+ stroke: color,
+ 'stroke-width': 2,
+ }),
+ );
}
return results;
};
@@ -337,10 +353,10 @@ export default (function() {
const { r } = this;
const x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
const y = this.offsetY + this.unitTime * commit.time;
- r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({
- fill: "#000",
- "fill-opacity": .5,
- stroke: "none"
+ r.path(['M', x + 5, y, 'L', x + 15, y + 4, 'L', x + 15, y - 4, 'Z']).attr({
+ fill: '#000',
+ 'fill-opacity': 0.5,
+ stroke: 'none',
});
// Displayed in the center
return this.element.scrollTop(y - this.graphHeight / 2);
diff --git a/app/assets/javascripts/network/raphael.js b/app/assets/javascripts/network/raphael.js
index 09dcf716148..22e06a35d91 100644
--- a/app/assets/javascripts/network/raphael.js
+++ b/app/assets/javascripts/network/raphael.js
@@ -49,7 +49,7 @@ Raphael.prototype.textWrap = function testWrap(t, width) {
const s = [];
for (let j = 0, len = words.length; j < len; j += 1) {
const word = words[j];
- if (x + (word.length * letterWidth) > width) {
+ if (x + word.length * letterWidth > width) {
s.push('\n');
x = 0;
}
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index 8f6ea9e61c1..f338dbbb0a6 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -30,24 +30,24 @@ export default class NewBranchForm {
startsWith = {
pattern: /^(\/|\.)/g,
prefix: "can't start with",
- conjunction: "or"
+ conjunction: 'or',
};
endsWith = {
pattern: /(\/|\.|\.lock)$/g,
prefix: "can't end in",
- conjunction: "or"
+ conjunction: 'or',
};
invalid = {
pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
prefix: "can't contain",
- conjunction: ", "
+ conjunction: ', ',
};
single = {
pattern: /^@+$/g,
prefix: "can't be",
- conjunction: "or"
+ conjunction: 'or',
};
- return this.restrictions = [startsWith, invalid, endsWith, single];
+ return (this.restrictions = [startsWith, invalid, endsWith, single]);
}
validate() {
@@ -73,7 +73,7 @@ export default class NewBranchForm {
return "'" + value + "'";
}
});
- return restriction.prefix + " " + (formatted.join(restriction.conjunction));
+ return restriction.prefix + ' ' + formatted.join(restriction.conjunction);
};
validator = (function(_this) {
return function(errors, restriction) {
@@ -88,7 +88,7 @@ export default class NewBranchForm {
})(this);
errors = this.restrictions.reduce(validator, []);
if (errors.length > 0) {
- errorMessage = $("<span/>").text(errors.join(', '));
+ errorMessage = $('<span/>').text(errors.join(', '));
return this.branchNameError.append(errorMessage);
}
}
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
index 17ec20f1cc1..b142f212eb0 100644
--- a/app/assets/javascripts/new_commit_form.js
+++ b/app/assets/javascripts/new_commit_form.js
@@ -6,9 +6,7 @@ export default class NewCommitForm {
this.branchName = form.find('.js-branch-name');
this.originalBranch = form.find('.js-original-branch');
this.createMergeRequest = form.find('.js-create-merge-request');
- this.createMergeRequestContainer = form.find(
- '.js-create-merge-request-container',
- );
+ this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
this.branchName.keyup(this.renderDestination);
this.renderDestination();
}
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index ad6e7cf501d..1f80f24e045 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -56,10 +56,11 @@ export default {
</script>
<template>
- <div class="line-resolve-all-container prepend-top-10">
+ <div
+ v-if="discussionCount > 0"
+ class="line-resolve-all-container prepend-top-8">
<div>
<div
- v-if="discussionCount > 0"
:class="{ 'has-next-btn': hasNextButton }"
class="line-resolve-all">
<span
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
new file mode 100644
index 00000000000..27972682ca1
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -0,0 +1,82 @@
+<script>
+import $ from 'jquery';
+import Icon from '~/vue_shared/components/icon.vue';
+import { mapGetters, mapActions } from 'vuex';
+
+export default {
+ components: {
+ Icon,
+ },
+ props: {
+ filters: {
+ type: Array,
+ required: true,
+ },
+ defaultValue: {
+ type: Number,
+ default: null,
+ required: false,
+ },
+ },
+ data() {
+ return { currentValue: this.defaultValue };
+ },
+ computed: {
+ ...mapGetters([
+ 'getNotesDataByProp',
+ ]),
+ currentFilter() {
+ if (!this.currentValue) return this.filters[0];
+ return this.filters.find(filter => filter.value === this.currentValue);
+ },
+ },
+ methods: {
+ ...mapActions(['filterDiscussion']),
+ selectFilter(value) {
+ const filter = parseInt(value, 10);
+
+ // close dropdown
+ $(this.$refs.dropdownToggle).dropdown('toggle');
+
+ if (filter === this.currentValue) return;
+ this.currentValue = filter;
+ this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter });
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="discussion-filter-container d-inline-block align-bottom">
+ <button
+ id="discussion-filter-dropdown"
+ ref="dropdownToggle"
+ class="btn btn-default"
+ data-toggle="dropdown"
+ aria-expanded="false"
+ >
+ {{ currentFilter.title }}
+ <icon name="chevron-down" />
+ </button>
+ <div
+ class="dropdown-menu dropdown-menu-selectable dropdown-menu-right"
+ aria-labelledby="discussion-filter-dropdown">
+ <div class="dropdown-content">
+ <ul>
+ <li
+ v-for="filter in filters"
+ :key="filter.value"
+ >
+ <button
+ :class="{ 'is-active': filter.value === currentValue }"
+ type="button"
+ @click="selectFilter(filter.value)"
+ >
+ {{ filter.title }}
+ </button>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 618a1581d8f..b0faa443a18 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -50,11 +50,11 @@ export default {
},
data() {
return {
- isLoading: true,
+ currentFilter: null,
};
},
computed: {
- ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount']),
+ ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount', 'isLoading']),
noteableType() {
return this.noteableData.noteableType;
},
@@ -102,6 +102,7 @@ export default {
},
methods: {
...mapActions({
+ setLoadingState: 'setLoadingState',
fetchDiscussions: 'fetchDiscussions',
poll: 'poll',
actionToggleAward: 'toggleAward',
@@ -133,19 +134,19 @@ export default {
return discussion.individual_note ? { note: discussion.notes[0] } : { discussion };
},
fetchNotes() {
- return this.fetchDiscussions(this.getNotesDataByProp('discussionsPath'))
+ return this.fetchDiscussions({ path: this.getNotesDataByProp('discussionsPath') })
.then(() => {
this.initPolling();
})
.then(() => {
- this.isLoading = false;
+ this.setLoadingState(false);
this.setNotesFetchedState(true);
eventHub.$emit('fetchedNotesData');
})
.then(() => this.$nextTick())
.then(() => this.checkLocationHash())
.catch(() => {
- this.isLoading = false;
+ this.setLoadingState(false);
this.setNotesFetchedState(true);
Flash('Something went wrong while fetching comments. Please try again.');
});
diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js
new file mode 100644
index 00000000000..012ffc4093e
--- /dev/null
+++ b/app/assets/javascripts/notes/discussion_filters.js
@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import DiscussionFilter from './components/discussion_filter.vue';
+
+export default (store) => {
+ const discussionFilterEl = document.getElementById('js-vue-discussion-filter');
+
+ if (discussionFilterEl) {
+ const { defaultFilter, notesFilters } = discussionFilterEl.dataset;
+ const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
+ const filterValues = notesFilters ? JSON.parse(notesFilters) : {};
+ const filters = Object.keys(filterValues).map(entry =>
+ ({ title: entry, value: filterValues[entry] }));
+
+ return new Vue({
+ el: discussionFilterEl,
+ name: 'DiscussionFilter',
+ components: {
+ DiscussionFilter,
+ },
+ store,
+ render(createElement) {
+ return createElement('discussion-filter', {
+ props: {
+ filters,
+ defaultValue,
+ },
+ });
+ },
+ });
+ }
+
+ return null;
+};
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 3aef30c608c..2f715c85fa6 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -1,10 +1,13 @@
import Vue from 'vue';
import notesApp from './components/notes_app.vue';
+import initDiscussionFilters from './discussion_filters';
import createStore from './stores';
document.addEventListener('DOMContentLoaded', () => {
const store = createStore();
+ initDiscussionFilters(store);
+
return new Vue({
el: '#js-vue-notes',
components: {
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index f5dce94caad..47a6f07cce2 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -5,8 +5,9 @@ import * as constants from '../constants';
Vue.use(VueResource);
export default {
- fetchDiscussions(endpoint) {
- return Vue.http.get(endpoint);
+ fetchDiscussions(endpoint, filter) {
+ const config = filter !== undefined ? { params: { notes_filter: filter } } : null;
+ return Vue.http.get(endpoint, config);
},
deleteNote(endpoint) {
return Vue.http.delete(endpoint);
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 7ab7e5a9abb..b5dd49bc6c9 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -11,6 +11,7 @@ import loadAwardsHandler from '../../awards_handler';
import sidebarTimeTrackingEventHub from '../../sidebar/event_hub';
import { isInViewport, scrollToElement } from '../../lib/utils/common_utils';
import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub';
+import { __ } from '~/locale';
let eTagPoll;
@@ -36,9 +37,9 @@ export const setNotesFetchedState = ({ commit }, state) =>
export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
-export const fetchDiscussions = ({ commit }, path) =>
+export const fetchDiscussions = ({ commit }, { path, filter }) =>
service
- .fetchDiscussions(path)
+ .fetchDiscussions(path, filter)
.then(res => res.json())
.then(discussions => {
commit(types.SET_INITIAL_DISCUSSIONS, discussions);
@@ -251,7 +252,7 @@ const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => {
if (discussion) {
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
} else if (note.type === constants.DIFF_NOTE) {
- dispatch('fetchDiscussions', state.notesData.discussionsPath);
+ dispatch('fetchDiscussions', { path: state.notesData.discussionsPath });
} else {
commit(types.ADD_NEW_NOTE, note);
}
@@ -345,5 +346,23 @@ export const updateMergeRequestWidget = () => {
mrWidgetEventHub.$emit('mr.discussion.updated');
};
+export const setLoadingState = ({ commit }, data) => {
+ commit(types.SET_NOTES_LOADING_STATE, data);
+};
+
+export const filterDiscussion = ({ dispatch }, { path, filter }) => {
+ dispatch('setLoadingState', true);
+ dispatch('fetchDiscussions', { path, filter })
+ .then(() => {
+ dispatch('setLoadingState', false);
+ dispatch('setNotesFetchedState', true);
+ })
+ .catch(() => {
+ dispatch('setLoadingState', false);
+ dispatch('setNotesFetchedState', true);
+ Flash(__('Something went wrong while fetching comments. Please try again.'));
+ });
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index a829149a17e..e4f36154fcd 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -1,6 +1,5 @@
import _ from 'underscore';
import * as constants from '../constants';
-import { reduceDiscussionsToLineCodes } from './utils';
import { collapseSystemNotes } from './collapse_utils';
export const discussions = state => collapseSystemNotes(state.discussions);
@@ -11,6 +10,8 @@ export const getNotesData = state => state.notesData;
export const isNotesFetched = state => state.isNotesFetched;
+export const isLoading = state => state.isLoading;
+
export const getNotesDataByProp = state => prop => state.notesData[prop];
export const getNoteableData = state => state.noteableData;
@@ -29,9 +30,6 @@ export const notesById = state =>
return acc;
}, {});
-export const discussionsStructuredByLineCode = state =>
- reduceDiscussionsToLineCodes(state.discussions);
-
export const noteableType = state => {
const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants;
diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js
index 61dbb075586..400142668ea 100644
--- a/app/assets/javascripts/notes/stores/modules/index.js
+++ b/app/assets/javascripts/notes/stores/modules/index.js
@@ -11,6 +11,7 @@ export default () => ({
// View layer
isToggleStateButtonLoading: false,
isNotesFetched: false,
+ isLoading: true,
// holds endpoints and permissions provided through haml
notesData: {
diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js
index 6f374f78691..2fa53aef1d4 100644
--- a/app/assets/javascripts/notes/stores/mutation_types.js
+++ b/app/assets/javascripts/notes/stores/mutation_types.js
@@ -14,6 +14,7 @@ export const UPDATE_NOTE = 'UPDATE_NOTE';
export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES';
export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE';
+export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
// DISCUSSION
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 73e55705f39..65085452139 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -216,6 +216,10 @@ export default {
Object.assign(state, { isNotesFetched: value });
},
+ [types.SET_NOTES_LOADING_STATE](state, value) {
+ state.isLoading = value;
+ },
+
[types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) {
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index 0e41ff03d67..dd57539e4d8 100644
--- a/app/assets/javascripts/notes/stores/utils.js
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -25,18 +25,6 @@ export const getQuickActionText = note => {
return text;
};
-export const reduceDiscussionsToLineCodes = selectedDiscussions =>
- selectedDiscussions.reduce((acc, note) => {
- if (note.diff_discussion && note.line_code) {
- // For context about line notes: there might be multiple notes with the same line code
- const items = acc[note.line_code] || [];
- items.push(note);
-
- Object.assign(acc, { [note.line_code]: items });
- }
- return acc;
- }, {});
-
export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note);
export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim();
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index 8ff8bb446ad..c4c8cf86cb0 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -18,7 +18,9 @@ export default function notificationsDropdown() {
$(document).on('ajax:success', '.notification-form', (e, data) => {
if (data.saved) {
- $(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html);
+ $(e.currentTarget)
+ .closest('.js-notification-dropdown')
+ .replaceWith(data.html);
} else {
Flash('Failed to save new settings', 'alert');
}
diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js
index 00e27ca0e70..45f033f2822 100644
--- a/app/assets/javascripts/notifications_form.js
+++ b/app/assets/javascripts/notifications_form.js
@@ -22,7 +22,8 @@ export default class NotificationsForm {
// eslint-disable-next-line class-methods-use-this
showCheckboxLoadingSpinner($parent) {
- $parent.addClass('is-loading')
+ $parent
+ .addClass('is-loading')
.find('.custom-notification-event-loading')
.removeClass('fa-check')
.addClass('fa-spin fa-spinner')
@@ -38,9 +39,12 @@ export default class NotificationsForm {
.then(({ data }) => {
$checkbox.enable();
if (data.saved) {
- $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
+ $parent
+ .find('.custom-notification-event-loading')
+ .toggleClass('fa-spin fa-spinner fa-check is-done');
setTimeout(() => {
- $parent.removeClass('is-loading')
+ $parent
+ .removeClass('is-loading')
.find('.custom-notification-event-loading')
.toggleClass('fa-spin fa-spinner fa-check is-done');
}, 2000);
diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js
index 86a43b66cc8..386a9b2c740 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -7,14 +7,21 @@ const ENDLESS_SCROLL_BOTTOM_PX = 400;
const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000;
export default {
- init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) {
+ init(
+ limit = 0,
+ preload = false,
+ disable = false,
+ prepareData = $.noop,
+ callback = $.noop,
+ container = '',
+ ) {
this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']);
this.limit = limit;
this.offset = parseInt(getParameterByName('offset'), 10) || this.limit;
this.disable = disable;
this.prepareData = prepareData;
this.callback = callback;
- this.loading = $('.loading').first();
+ this.loading = $(`${container} .loading`).first();
if (preload) {
this.offset = 0;
this.getOld();
@@ -24,22 +31,25 @@ export default {
getOld() {
this.loading.show();
- axios.get(this.url, {
- params: {
- limit: this.limit,
- offset: this.offset,
- },
- }).then(({ data }) => {
- this.append(data.count, this.prepareData(data.html));
- this.callback();
+ axios
+ .get(this.url, {
+ params: {
+ limit: this.limit,
+ offset: this.offset,
+ },
+ })
+ .then(({ data }) => {
+ this.append(data.count, this.prepareData(data.html));
+ this.callback();
- // keep loading until we've filled the viewport height
- if (!this.disable && !this.isScrollable()) {
- this.getOld();
- } else {
- this.loading.hide();
- }
- }).catch(() => this.loading.hide());
+ // keep loading until we've filled the viewport height
+ if (!this.disable && !this.isScrollable()) {
+ this.getOld();
+ } else {
+ this.loading.hide();
+ }
+ })
+ .catch(() => this.loading.hide());
},
append(count, html) {
diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
index 15e737fff05..d9cf62db3f7 100644
--- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
+++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js
@@ -31,7 +31,7 @@ export default class AbuseReports {
$messageCellElement.text(originalMessage);
} else {
$messageCellElement.data('messageTruncated', 'true');
- $messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`);
+ $messageCellElement.text(`${originalMessage.substr(0, MAX_MESSAGE_LENGTH - 3)}...`);
}
}
}
diff --git a/app/assets/javascripts/pages/admin/admin.js b/app/assets/javascripts/pages/admin/admin.js
index ff4d6ab15f9..4616a075729 100644
--- a/app/assets/javascripts/pages/admin/admin.js
+++ b/app/assets/javascripts/pages/admin/admin.js
@@ -23,7 +23,7 @@ export default function adminInit() {
}
});
- $('body').on('click', '.js-toggle-colors-link', (e) => {
+ $('body').on('click', '.js-toggle-colors-link', e => {
e.preventDefault();
$('.js-toggle-colors-container').toggleClass('hide');
});
@@ -33,12 +33,15 @@ export default function adminInit() {
$(this).tab('show');
});
- $('.log-bottom').on('click', (e) => {
+ $('.log-bottom').on('click', e => {
e.preventDefault();
const $visibleLog = $('.file-content:visible');
- $visibleLog.animate({
- scrollTop: $visibleLog.find('ol').height(),
- }, 'fast');
+ $visibleLog.animate(
+ {
+ scrollTop: $visibleLog.find('ol').height(),
+ },
+ 'fast',
+ );
});
$('.change-owner-link').on('click', function changeOwnerLinkClick(e) {
@@ -47,7 +50,7 @@ export default function adminInit() {
modal.show();
});
- $('.change-owner-cancel-link').on('click', (e) => {
+ $('.change-owner-cancel-link').on('click', e => {
e.preventDefault();
modal.hide();
$('.change-owner-link').show();
diff --git a/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js b/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js
index 7281f907ec7..455c637a6b3 100644
--- a/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js
+++ b/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js
@@ -1,10 +1,14 @@
import { __ } from '~/locale';
export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE = __('Regex pattern');
-export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE = __('To define internal users, first enable new users set to external');
+export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE = __(
+ 'To define internal users, first enable new users set to external',
+);
function setUserInternalRegexPlaceholder(checkbox) {
- const userInternalRegex = document.getElementById('application_setting_user_default_internal_regex');
+ const userInternalRegex = document.getElementById(
+ 'application_setting_user_default_internal_regex',
+ );
if (checkbox && userInternalRegex) {
if (checkbox.checked) {
userInternalRegex.readOnly = false;
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index e7ceccb6f47..d5ded3f9a79 100644
--- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
@@ -17,20 +17,24 @@ export default () => {
const previewPath = $('textarea#broadcast_message_message').data('previewPath');
- $('textarea#broadcast_message_message').on('input', _.debounce(function onMessageInput() {
- const message = $(this).val();
- if (message === '') {
- $('.js-broadcast-message-preview').text('Your message here');
- } else {
- axios.post(previewPath, {
- broadcast_message: {
- message,
- },
- })
- .then(({ data }) => {
- $('.js-broadcast-message-preview').html(data.message);
- })
- .catch(() => flash(__('An error occurred while rendering preview broadcast message')));
- }
- }, 250));
+ $('textarea#broadcast_message_message').on(
+ 'input',
+ _.debounce(function onMessageInput() {
+ const message = $(this).val();
+ if (message === '') {
+ $('.js-broadcast-message-preview').text('Your message here');
+ } else {
+ axios
+ .post(previewPath, {
+ broadcast_message: {
+ message,
+ },
+ })
+ .then(({ data }) => {
+ $('.js-broadcast-message-preview').html(data.message);
+ })
+ .catch(() => flash(__('An error occurred while rendering preview broadcast message')));
+ }
+ }, 250),
+ );
};
diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
index bc84666779e..e2fec3c7172 100644
--- a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
+++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue
@@ -1,39 +1,42 @@
<script>
- import axios from '~/lib/utils/axios_utils';
- import createFlash from '~/flash';
- import GlModal from '~/vue_shared/components/gl_modal.vue';
- import { redirectTo } from '~/lib/utils/url_utility';
- import { s__ } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import { redirectTo } from '~/lib/utils/url_utility';
+import { s__ } from '~/locale';
- export default {
- components: {
- GlModal,
+export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ url: {
+ type: String,
+ required: true,
},
- props: {
- url: {
- type: String,
- required: true,
- },
+ },
+ computed: {
+ text() {
+ return s__(
+ 'AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running.',
+ );
},
- computed: {
- text() {
- return s__('AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running.');
- },
+ },
+ methods: {
+ onSubmit() {
+ return axios
+ .post(this.url)
+ .then(response => {
+ // follow the rediect to refresh the page
+ redirectTo(response.request.responseURL);
+ })
+ .catch(error => {
+ createFlash(s__('AdminArea|Stopping jobs failed'));
+ throw error;
+ });
},
- methods: {
- onSubmit() {
- return axios.post(this.url)
- .then((response) => {
- // follow the rediect to refresh the page
- redirectTo(response.request.responseURL);
- })
- .catch((error) => {
- createFlash(s__('AdminArea|Stopping jobs failed'));
- throw error;
- });
- },
- },
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/pages/admin/projects/index.js b/app/assets/javascripts/pages/admin/projects/index.js
index 31c96eb87af..d6b1e747aec 100644
--- a/app/assets/javascripts/pages/admin/projects/index.js
+++ b/app/assets/javascripts/pages/admin/projects/index.js
@@ -4,6 +4,7 @@ import NamespaceSelect from '../../../namespace_select';
document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new
- document.querySelectorAll('.js-namespace-select')
+ document
+ .querySelectorAll('.js-namespace-select')
.forEach(dropdown => new NamespaceSelect({ dropdown }));
});
diff --git a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
index ff66d3a8ac4..3c383735f4a 100644
--- a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
+++ b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue
@@ -1,81 +1,84 @@
<script>
- import _ from 'underscore';
- import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
- import { s__, sprintf } from '~/locale';
+import _ from 'underscore';
+import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import { s__, sprintf } from '~/locale';
- export default {
- components: {
- DeprecatedModal,
+export default {
+ components: {
+ DeprecatedModal,
+ },
+ props: {
+ deleteProjectUrl: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- deleteProjectUrl: {
- type: String,
- required: false,
- default: '',
- },
- projectName: {
- type: String,
- required: false,
- default: '',
- },
- csrfToken: {
- type: String,
- required: false,
- default: '',
- },
+ projectName: {
+ type: String,
+ required: false,
+ default: '',
},
- data() {
- return {
- enteredProjectName: '',
- };
+ csrfToken: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- title() {
- return sprintf(s__('AdminProjects|Delete Project %{projectName}?'),
- {
- projectName: `'${_.escape(this.projectName)}'`,
- },
- false,
- );
- },
- text() {
- return sprintf(s__(`AdminProjects|
+ },
+ data() {
+ return {
+ enteredProjectName: '',
+ };
+ },
+ computed: {
+ title() {
+ return sprintf(
+ s__('AdminProjects|Delete Project %{projectName}?'),
+ {
+ projectName: `'${_.escape(this.projectName)}'`,
+ },
+ false,
+ );
+ },
+ text() {
+ return sprintf(
+ s__(`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.`),
- {
- projectName: `<strong>${_.escape(this.projectName)}</strong>`,
- strong_start: '<strong>',
- strong_end: '</strong>',
- },
- false,
- );
- },
- confirmationTextLabel() {
- return sprintf(s__('AdminUsers|To confirm, type %{projectName}'),
- {
- projectName: `<code>${_.escape(this.projectName)}</code>`,
- },
- false,
- );
- },
- primaryButtonLabel() {
- return s__('AdminProjects|Delete project');
- },
- canSubmit() {
- return this.enteredProjectName === this.projectName;
- },
+ {
+ projectName: `<strong>${_.escape(this.projectName)}</strong>`,
+ strong_start: '<strong>',
+ strong_end: '</strong>',
+ },
+ false,
+ );
+ },
+ confirmationTextLabel() {
+ return sprintf(
+ s__('AdminUsers|To confirm, type %{projectName}'),
+ {
+ projectName: `<code>${_.escape(this.projectName)}</code>`,
+ },
+ false,
+ );
+ },
+ primaryButtonLabel() {
+ return s__('AdminProjects|Delete project');
+ },
+ canSubmit() {
+ return this.enteredProjectName === this.projectName;
+ },
+ },
+ methods: {
+ onCancel() {
+ this.enteredProjectName = '';
},
- methods: {
- onCancel() {
- this.enteredProjectName = '';
- },
- onSubmit() {
- this.$refs.form.submit();
- this.enteredProjectName = '';
- },
+ onSubmit() {
+ this.$refs.form.submit();
+ this.enteredProjectName = '';
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/pages/admin/projects/index/index.js b/app/assets/javascripts/pages/admin/projects/index/index.js
index ddbefec87b6..6fa8760545d 100644
--- a/app/assets/javascripts/pages/admin/projects/index/index.js
+++ b/app/assets/javascripts/pages/admin/projects/index/index.js
@@ -28,7 +28,7 @@ document.addEventListener('DOMContentLoaded', () => {
},
});
- $(document).on('shown.bs.modal', (event) => {
+ $(document).on('shown.bs.modal', event => {
if (event.relatedTarget.classList.contains('delete-project-button')) {
const buttonProps = event.relatedTarget.dataset;
deleteModal.deleteProjectUrl = buttonProps.deleteProjectUrl;
diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
index 8d5efcdcd96..4b33fcc759a 100644
--- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
+++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue
@@ -1,114 +1,119 @@
<script>
- import _ from 'underscore';
- import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
- import { s__, sprintf } from '~/locale';
+import _ from 'underscore';
+import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import { s__, sprintf } from '~/locale';
- export default {
- components: {
- DeprecatedModal,
+export default {
+ components: {
+ DeprecatedModal,
+ },
+ props: {
+ deleteUserUrl: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- deleteUserUrl: {
- type: String,
- required: false,
- default: '',
- },
- blockUserUrl: {
- type: String,
- required: false,
- default: '',
- },
- deleteContributions: {
- type: Boolean,
- required: false,
- default: false,
- },
- username: {
- type: String,
- required: false,
- default: '',
- },
- csrfToken: {
- type: String,
- required: false,
- default: '',
- },
+ blockUserUrl: {
+ type: String,
+ required: false,
+ default: '',
},
- data() {
- return {
- enteredUsername: '',
- };
+ deleteContributions: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- computed: {
- title() {
- const keepContributionsTitle = s__('AdminUsers|Delete User %{username}?');
- const deleteContributionsTitle = s__('AdminUsers|Delete User %{username} and contributions?');
+ username: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ csrfToken: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ enteredUsername: '',
+ };
+ },
+ computed: {
+ title() {
+ const keepContributionsTitle = s__('AdminUsers|Delete User %{username}?');
+ const deleteContributionsTitle = s__('AdminUsers|Delete User %{username} and contributions?');
- return sprintf(
- this.deleteContributions ? deleteContributionsTitle : keepContributionsTitle, {
- username: `'${_.escape(this.username)}'`,
- }, false);
- },
- text() {
- const keepContributionsText = s__(`AdminArea|
+ return sprintf(
+ this.deleteContributions ? deleteContributionsTitle : keepContributionsTitle,
+ {
+ username: `'${_.escape(this.username)}'`,
+ },
+ false,
+ );
+ },
+ text() {
+ const keepContributionsText = s__(`AdminArea|
You are about to permanently delete the user %{username}.
Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user".
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
- const deleteContributionsText = s__(`AdminArea|
+ const deleteContributionsText = s__(`AdminArea|
You are about to permanently delete the user %{username}.
This will delete all of the issues, merge requests, and groups linked to them.
To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead.
Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`);
- return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText,
- {
- username: `<strong>${_.escape(this.username)}</strong>`,
- strong_start: '<strong>',
- strong_end: '</strong>',
- },
- false,
- );
- },
- confirmationTextLabel() {
- return sprintf(s__('AdminUsers|To confirm, type %{username}'),
- {
- username: `<code>${_.escape(this.username)}</code>`,
- },
- false,
- );
- },
- primaryButtonLabel() {
- const keepContributionsLabel = s__('AdminUsers|Delete user');
- const deleteContributionsLabel = s__('AdminUsers|Delete user and contributions');
+ return sprintf(
+ this.deleteContributions ? deleteContributionsText : keepContributionsText,
+ {
+ username: `<strong>${_.escape(this.username)}</strong>`,
+ strong_start: '<strong>',
+ strong_end: '</strong>',
+ },
+ false,
+ );
+ },
+ confirmationTextLabel() {
+ return sprintf(
+ s__('AdminUsers|To confirm, type %{username}'),
+ {
+ username: `<code>${_.escape(this.username)}</code>`,
+ },
+ false,
+ );
+ },
+ primaryButtonLabel() {
+ const keepContributionsLabel = s__('AdminUsers|Delete user');
+ const deleteContributionsLabel = s__('AdminUsers|Delete user and contributions');
- return this.deleteContributions ? deleteContributionsLabel : keepContributionsLabel;
- },
- secondaryButtonLabel() {
- return s__('AdminUsers|Block user');
- },
- canSubmit() {
- return this.enteredUsername === this.username;
- },
+ return this.deleteContributions ? deleteContributionsLabel : keepContributionsLabel;
},
- methods: {
- onCancel() {
- this.enteredUsername = '';
- },
- onSecondaryAction() {
- const { form } = this.$refs;
+ secondaryButtonLabel() {
+ return s__('AdminUsers|Block user');
+ },
+ canSubmit() {
+ return this.enteredUsername === this.username;
+ },
+ },
+ methods: {
+ onCancel() {
+ this.enteredUsername = '';
+ },
+ onSecondaryAction() {
+ const { form } = this.$refs;
- form.action = this.blockUserUrl;
- this.$refs.method.value = 'put';
+ form.action = this.blockUserUrl;
+ this.$refs.method.value = 'put';
- form.submit();
- },
- onSubmit() {
- this.$refs.form.submit();
- this.enteredUsername = '';
- },
+ form.submit();
+ },
+ onSubmit() {
+ this.$refs.form.submit();
+ this.enteredUsername = '';
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js
index 06599c3fd5f..45046688b57 100644
--- a/app/assets/javascripts/pages/admin/users/index.js
+++ b/app/assets/javascripts/pages/admin/users/index.js
@@ -32,12 +32,14 @@ document.addEventListener('DOMContentLoaded', () => {
},
});
- $(document).on('shown.bs.modal', (event) => {
+ $(document).on('shown.bs.modal', event => {
if (event.relatedTarget.classList.contains('delete-user-button')) {
const buttonProps = event.relatedTarget.dataset;
deleteModal.deleteUserUrl = buttonProps.deleteUserUrl;
deleteModal.blockUserUrl = buttonProps.blockUserUrl;
- deleteModal.deleteContributions = event.relatedTarget.hasAttribute('data-delete-contributions');
+ deleteModal.deleteContributions = event.relatedTarget.hasAttribute(
+ 'data-delete-contributions',
+ );
deleteModal.username = buttonProps.username;
}
});
diff --git a/app/assets/javascripts/pages/admin/users/new/index.js b/app/assets/javascripts/pages/admin/users/new/index.js
index 58bfa8d64e7..3e6a090cb0e 100644
--- a/app/assets/javascripts/pages/admin/users/new/index.js
+++ b/app/assets/javascripts/pages/admin/users/new/index.js
@@ -4,7 +4,9 @@ export default class UserInternalRegexHandler {
constructor() {
this.regexPattern = $('[data-user-internal-regex-pattern]').data('user-internal-regex-pattern');
if (this.regexPattern && this.regexPattern !== '') {
- this.regexOptions = $('[data-user-internal-regex-options]').data('user-internal-regex-options');
+ this.regexOptions = $('[data-user-internal-regex-options]').data(
+ 'user-internal-regex-options',
+ );
this.external = $('#user_external');
this.warningMessage = $('#warning_external_automatically_set');
this.addListenerToEmailField();
@@ -13,7 +15,7 @@ export default class UserInternalRegexHandler {
}
addListenerToEmailField() {
- $('#user_email').on('input', (event) => {
+ $('#user_email').on('input', event => {
this.setExternalCheckbox(event.currentTarget.value);
});
}
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index 72f3f70b98f..1b56b97f751 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -79,7 +79,8 @@ export default class Todos {
.then(({ data }) => {
this.updateRowState(target);
this.updateBadges(data);
- }).catch(() => {
+ })
+ .catch(() => {
this.updateRowState(target, true);
return flash(__('Error updating todo status.'));
});
@@ -118,10 +119,12 @@ export default class Todos {
axios[target.dataset.method](target.dataset.href, {
ids: this.todo_ids,
- }).then(({ data }) => {
- this.updateAllState(target, data);
- this.updateBadges(data);
- }).catch(() => flash(__('Error updating status for all todos.')));
+ })
+ .then(({ data }) => {
+ this.updateAllState(target, data);
+ this.updateBadges(data);
+ })
+ .catch(() => flash(__('Error updating status for all todos.')));
}
updateAllState(target, data) {
@@ -133,7 +136,7 @@ export default class Todos {
target.removeAttribute('disabled');
target.classList.remove('disabled');
- this.todo_ids = (target === markAllDoneBtn) ? data.updated_ids : [];
+ this.todo_ids = target === markAllDoneBtn ? data.updated_ids : [];
undoAllBtn.classList.toggle('hidden');
markAllDoneBtn.classList.toggle('hidden');
todoListContainer.classList.toggle('hidden');
diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
index d0bce857029..32b55575f95 100644
--- a/app/assets/javascripts/pages/groups/edit/index.js
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -5,6 +5,7 @@ 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';
+import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => {
groupAvatar();
@@ -15,4 +16,6 @@ document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'),
);
mountBadgeSettings(GROUP_BADGE);
+
+ projectSelect();
});
diff --git a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue
index 48668562f09..a4778077bc4 100644
--- a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue
+++ b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue
@@ -1,94 +1,117 @@
<script>
- import axios from '~/lib/utils/axios_utils';
+import axios from '~/lib/utils/axios_utils';
- import Flash from '~/flash';
- import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
- import { n__, s__, sprintf } from '~/locale';
- import { redirectTo } from '~/lib/utils/url_utility';
- import eventHub from '../event_hub';
+import Flash from '~/flash';
+import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import { n__, s__, sprintf } from '~/locale';
+import { redirectTo } from '~/lib/utils/url_utility';
+import eventHub from '../event_hub';
- export default {
- components: {
- DeprecatedModal,
+export default {
+ components: {
+ DeprecatedModal,
+ },
+ props: {
+ issueCount: {
+ type: Number,
+ required: true,
},
- props: {
- issueCount: {
- type: Number,
- required: true,
- },
- mergeRequestCount: {
- type: Number,
- required: true,
- },
- milestoneId: {
- type: Number,
- required: true,
- },
- milestoneTitle: {
- type: String,
- required: true,
- },
- milestoneUrl: {
- type: String,
- required: true,
- },
+ mergeRequestCount: {
+ type: Number,
+ required: true,
},
- computed: {
- text() {
- const milestoneTitle = sprintf('<strong>%{milestoneTitle}</strong>', { milestoneTitle: this.milestoneTitle });
-
- if (this.issueCount === 0 && this.mergeRequestCount === 0) {
- return sprintf(
- s__(`Milestones|
-You’re about to permanently delete the milestone %{milestoneTitle}.
-This milestone is not currently used in any issues or merge requests.`),
- {
- milestoneTitle,
- },
- false,
- );
- }
+ milestoneId: {
+ type: Number,
+ required: true,
+ },
+ milestoneTitle: {
+ type: String,
+ required: true,
+ },
+ milestoneUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ text() {
+ const milestoneTitle = sprintf('<strong>%{milestoneTitle}</strong>', {
+ milestoneTitle: this.milestoneTitle,
+ });
+ if (this.issueCount === 0 && this.mergeRequestCount === 0) {
return sprintf(
s__(`Milestones|
-You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}.
-Once deleted, it cannot be undone or recovered.`),
+You’re about to permanently delete the milestone %{milestoneTitle}.
+This milestone is not currently used in any issues or merge requests.`),
{
milestoneTitle,
- issuesWithCount: n__('%d issue', '%d issues', this.issueCount),
- mergeRequestsWithCount: n__('%d merge request', '%d merge requests', this.mergeRequestCount),
},
false,
);
- },
- title() {
- return sprintf(s__('Milestones|Delete milestone %{milestoneTitle}?'), { milestoneTitle: this.milestoneTitle });
- },
- },
- methods: {
- onSubmit() {
- eventHub.$emit('deleteMilestoneModal.requestStarted', this.milestoneUrl);
+ }
- return axios.delete(this.milestoneUrl)
- .then((response) => {
- eventHub.$emit('deleteMilestoneModal.requestFinished', { milestoneUrl: this.milestoneUrl, successful: true });
+ return sprintf(
+ s__(`Milestones|
+You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}.
+Once deleted, it cannot be undone or recovered.`),
+ {
+ milestoneTitle,
+ issuesWithCount: n__('%d issue', '%d issues', this.issueCount),
+ mergeRequestsWithCount: n__(
+ '%d merge request',
+ '%d merge requests',
+ this.mergeRequestCount,
+ ),
+ },
+ false,
+ );
+ },
+ title() {
+ return sprintf(s__('Milestones|Delete milestone %{milestoneTitle}?'), {
+ milestoneTitle: this.milestoneTitle,
+ });
+ },
+ },
+ methods: {
+ onSubmit() {
+ eventHub.$emit('deleteMilestoneModal.requestStarted', this.milestoneUrl);
- // follow the rediect to milestones overview page
- redirectTo(response.request.responseURL);
- })
- .catch((error) => {
- eventHub.$emit('deleteMilestoneModal.requestFinished', { milestoneUrl: this.milestoneUrl, successful: false });
+ return axios
+ .delete(this.milestoneUrl)
+ .then(response => {
+ eventHub.$emit('deleteMilestoneModal.requestFinished', {
+ milestoneUrl: this.milestoneUrl,
+ successful: true,
+ });
- if (error.response && error.response.status === 404) {
- Flash(sprintf(s__('Milestones|Milestone %{milestoneTitle} was not found'), { milestoneTitle: this.milestoneTitle }));
- } else {
- Flash(sprintf(s__('Milestones|Failed to delete milestone %{milestoneTitle}'), { milestoneTitle: this.milestoneTitle }));
- }
- throw error;
+ // follow the rediect to milestones overview page
+ redirectTo(response.request.responseURL);
+ })
+ .catch(error => {
+ eventHub.$emit('deleteMilestoneModal.requestFinished', {
+ milestoneUrl: this.milestoneUrl,
+ successful: false,
});
- },
+
+ if (error.response && error.response.status === 404) {
+ Flash(
+ sprintf(s__('Milestones|Milestone %{milestoneTitle} was not found'), {
+ milestoneTitle: this.milestoneTitle,
+ }),
+ );
+ } else {
+ Flash(
+ sprintf(s__('Milestones|Failed to delete milestone %{milestoneTitle}'), {
+ milestoneTitle: this.milestoneTitle,
+ }),
+ );
+ }
+ throw error;
+ });
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js b/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js
index d51b5c221e3..1d559dc6e41 100644
--- a/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js
+++ b/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js
@@ -7,7 +7,9 @@ export default () => {
Vue.use(Translate);
const onRequestFinished = ({ milestoneUrl, successful }) => {
- const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
+ const button = document.querySelector(
+ `.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`,
+ );
if (!successful) {
button.removeAttribute('disabled');
@@ -16,14 +18,16 @@ export default () => {
button.querySelector('.js-loading-icon').classList.add('hidden');
};
- const onRequestStarted = (milestoneUrl) => {
- const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`);
+ const onRequestStarted = milestoneUrl => {
+ const button = document.querySelector(
+ `.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`,
+ );
button.setAttribute('disabled', '');
button.querySelector('.js-loading-icon').classList.remove('hidden');
eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished);
};
- const onDeleteButtonClick = (event) => {
+ const onDeleteButtonClick = event => {
const button = event.currentTarget;
const modalProps = {
milestoneId: parseInt(button.dataset.milestoneId, 10),
@@ -37,12 +41,12 @@ export default () => {
};
const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button');
- deleteMilestoneButtons.forEach((button) => {
+ deleteMilestoneButtons.forEach(button => {
button.addEventListener('click', onDeleteButtonClick);
});
eventHub.$once('deleteMilestoneModal.mounted', () => {
- deleteMilestoneButtons.forEach((button) => {
+ deleteMilestoneButtons.forEach(button => {
button.removeAttribute('disabled');
});
});
diff --git a/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js b/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js
index 8e79341e96a..fcc62a2b2af 100644
--- a/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js
+++ b/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js
@@ -7,20 +7,24 @@ Vue.use(Translate);
export default () => {
const onRequestFinished = ({ milestoneUrl, successful }) => {
- const button = document.querySelector(`.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`);
+ const button = document.querySelector(
+ `.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`,
+ );
if (!successful) {
button.removeAttribute('disabled');
}
};
- const onRequestStarted = (milestoneUrl) => {
- const button = document.querySelector(`.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`);
+ const onRequestStarted = milestoneUrl => {
+ const button = document.querySelector(
+ `.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`,
+ );
button.setAttribute('disabled', '');
eventHub.$once('promoteMilestoneModal.requestFinished', onRequestFinished);
};
- const onDeleteButtonClick = (event) => {
+ const onDeleteButtonClick = event => {
const button = event.currentTarget;
const modalProps = {
milestoneTitle: button.dataset.milestoneTitle,
@@ -32,12 +36,12 @@ export default () => {
};
const promoteMilestoneButtons = document.querySelectorAll('.js-promote-project-milestone-button');
- promoteMilestoneButtons.forEach((button) => {
+ promoteMilestoneButtons.forEach(button => {
button.addEventListener('click', onDeleteButtonClick);
});
eventHub.$once('promoteMilestoneModal.mounted', () => {
- promoteMilestoneButtons.forEach((button) => {
+ promoteMilestoneButtons.forEach(button => {
button.removeAttribute('disabled');
});
});
diff --git a/app/assets/javascripts/pages/profiles/index.js b/app/assets/javascripts/pages/profiles/index.js
index 04e50963699..883be18b336 100644
--- a/app/assets/javascripts/pages/profiles/index.js
+++ b/app/assets/javascripts/pages/profiles/index.js
@@ -3,9 +3,12 @@ import '~/profile/gl_crop';
import Profile from '~/profile/profile';
document.addEventListener('DOMContentLoaded', () => {
- $(document).on('input.ssh_key', '#key_key', function () { // eslint-disable-line func-names
+ // eslint-disable-next-line func-names
+ $(document).on('input.ssh_key', '#key_key', function() {
const $title = $('#key_title');
- const comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/);
+ const comment = $(this)
+ .val()
+ .match(/^\S+ \S+ (.+)\n?$/);
// Extract the SSH Key title from its comment
if (comment && comment.length > 1) {
diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
index 8e8f47c21d8..417935e2ad0 100644
--- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
+++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js
@@ -5,7 +5,9 @@ document.addEventListener('DOMContentLoaded', () => {
const twoFactorNode = document.querySelector('.js-two-factor-auth');
const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true';
if (skippable) {
- const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
+ const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${
+ twoFactorNode.dataset.two_factor_skip_url
+ }">Configure it later</a>`;
const flashAlert = document.querySelector('.flash-alert .container-fluid');
if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button);
}
diff --git a/app/assets/javascripts/pages/projects/branches/new/index.js b/app/assets/javascripts/pages/projects/branches/new/index.js
index a9658fd1eb4..13ff47d53c2 100644
--- a/app/assets/javascripts/pages/projects/branches/new/index.js
+++ b/app/assets/javascripts/pages/projects/branches/new/index.js
@@ -1,6 +1,11 @@
import $ from 'jquery';
import NewBranchForm from '~/new_branch_form';
-document.addEventListener('DOMContentLoaded', () => (
- new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML))
-));
+document.addEventListener(
+ 'DOMContentLoaded',
+ () =>
+ new NewBranchForm(
+ $('.js-create-branch-form'),
+ JSON.parse(document.getElementById('availableRefs').innerHTML),
+ ),
+);
diff --git a/app/assets/javascripts/pages/projects/graphs/charts/index.js b/app/assets/javascripts/pages/projects/graphs/charts/index.js
index 80159a82bd4..3ccad513c05 100644
--- a/app/assets/javascripts/pages/projects/graphs/charts/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/charts/index.js
@@ -31,14 +31,16 @@ document.addEventListener('DOMContentLoaded', () => {
const chartData = data => ({
labels: Object.keys(data),
- datasets: [{
- fillColor: 'rgba(220,220,220,0.5)',
- strokeColor: 'rgba(220,220,220,1)',
- barStrokeWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data: _.values(data),
- }],
+ datasets: [
+ {
+ fillColor: 'rgba(220,220,220,0.5)',
+ strokeColor: 'rgba(220,220,220,1)',
+ barStrokeWidth: 1,
+ barValueSpacing: 1,
+ barDatasetSpacing: 1,
+ data: _.values(data),
+ },
+ ],
});
const hourData = chartData(projectChartData.hour);
@@ -51,7 +53,9 @@ document.addEventListener('DOMContentLoaded', () => {
responsiveChart($('#month-chart'), monthData);
const data = projectChartData.languages;
- const ctx = $('#languages-chart').get(0).getContext('2d');
+ const ctx = $('#languages-chart')
+ .get(0)
+ .getContext('2d');
const options = {
scaleOverlay: true,
responsive: true,
diff --git a/app/assets/javascripts/pages/projects/graphs/show/index.js b/app/assets/javascripts/pages/projects/graphs/show/index.js
index 71f629fbc13..f79c386b59e 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/index.js
@@ -7,7 +7,8 @@ import ContributorsStatGraph from './stat_graph_contributors';
document.addEventListener('DOMContentLoaded', () => {
const url = document.querySelector('.js-graphs-show').dataset.projectGraphPath;
- axios.get(url)
+ axios
+ .get(url)
.then(({ data }) => {
const graph = new ContributorsStatGraph();
graph.init(data);
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
index 58bb8c5b0c8..76613394af6 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js
@@ -3,7 +3,11 @@
import $ from 'jquery';
import _ from 'underscore';
import { n__, s__, createDateTimeFormat, sprintf } from '~/locale';
-import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph';
+import {
+ ContributorsGraph,
+ ContributorsAuthorGraph,
+ ContributorsMasterGraph,
+} from './stat_graph_contributors_graph';
import ContributorsStatGraphUtil from './stat_graph_contributors_util';
export default (function() {
@@ -14,7 +18,7 @@ export default (function() {
ContributorsStatGraph.prototype.init = function(log) {
var author_commits, total_commits;
this.parsed_log = ContributorsStatGraphUtil.parse_log(log);
- this.set_current_field("commits");
+ this.set_current_field('commits');
total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field);
author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field);
this.add_master_graph(total_commits);
@@ -31,23 +35,26 @@ export default (function() {
var limited_author_data;
this.authors = [];
limited_author_data = author_data.slice(0, 100);
- return _.each(limited_author_data, (function(_this) {
- return function(d) {
- var author_graph, author_header;
- author_header = _this.create_author_header(d);
- $(".contributors-list").append(author_header);
-
- author_graph = new ContributorsAuthorGraph(d.dates);
- _this.authors[d.author_name] = author_graph;
- return author_graph.draw();
- };
- })(this));
+ return _.each(
+ limited_author_data,
+ (function(_this) {
+ return function(d) {
+ var author_graph, author_header;
+ author_header = _this.create_author_header(d);
+ $('.contributors-list').append(author_header);
+
+ author_graph = new ContributorsAuthorGraph(d.dates);
+ _this.authors[d.author_name] = author_graph;
+ return author_graph.draw();
+ };
+ })(this),
+ );
};
ContributorsStatGraph.prototype.format_author_commit_info = function(author) {
var commits;
commits = $('<span/>', {
- "class": 'graph-author-commits-count'
+ class: 'graph-author-commits-count',
});
commits.text(n__('%d commit', '%d commits', author.commits));
return $('<span/>').append(commits);
@@ -56,13 +63,13 @@ export default (function() {
ContributorsStatGraph.prototype.create_author_header = function(author) {
var author_commit_info, author_commit_info_span, author_email, author_name, list_item;
list_item = $('<li/>', {
- "class": 'person',
- style: 'display: block;'
+ class: 'person',
+ style: 'display: block;',
});
author_name = $('<h4>' + author.author_name + '</h4>');
author_email = $('<p class="graph-author-email">' + author.author_email + '</p>');
author_commit_info_span = $('<span/>', {
- "class": 'commits'
+ class: 'commits',
});
author_commit_info = this.format_author_commit_info(author);
author_commit_info_span.html(author_commit_info);
@@ -80,37 +87,41 @@ export default (function() {
};
ContributorsStatGraph.prototype.redraw_authors = function() {
- $("ol").html("");
+ $('ol').html('');
const { x_domain } = ContributorsGraph.prototype;
- const author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain);
-
- return _.each(author_commits, (function(_this) {
- return function(d) {
- _this.redraw_author_commit_info(d);
- if (_this.authors[d.author_name] != null) {
- $(_this.authors[d.author_name].list_item).appendTo("ol");
- _this.authors[d.author_name].set_data(d.dates);
- return _this.authors[d.author_name].redraw();
- }
- return '';
- };
- })(this));
+ const author_commits = ContributorsStatGraphUtil.get_author_data(
+ this.parsed_log,
+ this.field,
+ x_domain,
+ );
+
+ return _.each(
+ author_commits,
+ (function(_this) {
+ return function(d) {
+ _this.redraw_author_commit_info(d);
+ if (_this.authors[d.author_name] != null) {
+ $(_this.authors[d.author_name].list_item).appendTo('ol');
+ _this.authors[d.author_name].set_data(d.dates);
+ return _this.authors[d.author_name].redraw();
+ }
+ return '';
+ };
+ })(this),
+ );
};
ContributorsStatGraph.prototype.set_current_field = function(field) {
- return this.field = field;
+ return (this.field = field);
};
ContributorsStatGraph.prototype.change_date_header = function() {
const { x_domain } = ContributorsGraph.prototype;
- const formattedDateRange = sprintf(
- s__('ContributorsPage|%{startDate} – %{endDate}'),
- {
- startDate: this.dateFormat.format(new Date(x_domain[0])),
- endDate: this.dateFormat.format(new Date(x_domain[1])),
- },
- );
+ const formattedDateRange = sprintf(s__('ContributorsPage|%{startDate} – %{endDate}'), {
+ startDate: this.dateFormat.format(new Date(x_domain[0])),
+ endDate: this.dateFormat.format(new Date(x_domain[1])),
+ });
return $('#date_header').text(formattedDateRange);
};
@@ -120,7 +131,7 @@ export default (function() {
if ($author != null) {
author_list_item = $(this.authors[author.author_name].list_item);
author_commit_info = this.format_author_commit_info(author);
- return author_list_item.find("span").html(author_commit_info);
+ return author_list_item.find('span').html(author_commit_info);
}
return '';
};
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
index 5f91686347a..377dce6c746 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js
@@ -11,10 +11,32 @@ import { brushX } from 'd3-brush';
import { timeParse } from 'd3-time-format';
import { dateTickFormat } from '~/lib/utils/tick_formats';
-const d3 = { extent, max, select, scaleTime, scaleLinear, axisLeft, axisBottom, area, brushX, timeParse };
+const d3 = {
+ extent,
+ max,
+ select,
+ scaleTime,
+ scaleLinear,
+ axisLeft,
+ axisBottom,
+ area,
+ brushX,
+ timeParse,
+};
const hasProp = {}.hasOwnProperty;
-const extend = function(child, parent) { for (const key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
+const extend = function(child, parent) {
+ for (const key in parent) {
+ if (hasProp.call(parent, key)) child[key] = parent[key];
+ }
+ function ctor() {
+ this.constructor = child;
+ }
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor();
+ child.__super__ = parent.prototype;
+ return child;
+};
export const ContributorsGraph = (function() {
function ContributorsGraph() {}
@@ -23,7 +45,7 @@ export const ContributorsGraph = (function() {
top: 20,
right: 10,
bottom: 30,
- left: 40
+ left: 40,
};
ContributorsGraph.prototype.x_domain = null;
@@ -33,35 +55,39 @@ export const ContributorsGraph = (function() {
ContributorsGraph.prototype.dates = [];
ContributorsGraph.prototype.determine_width = function(baseWidth, $parentElement) {
- const parentPaddingWidth = parseFloat($parentElement.css('padding-left')) + parseFloat($parentElement.css('padding-right'));
+ const parentPaddingWidth =
+ parseFloat($parentElement.css('padding-left')) +
+ parseFloat($parentElement.css('padding-right'));
const marginWidth = this.MARGIN.left + this.MARGIN.right;
return baseWidth - parentPaddingWidth - marginWidth;
};
ContributorsGraph.set_x_domain = function(data) {
- return ContributorsGraph.prototype.x_domain = data;
+ return (ContributorsGraph.prototype.x_domain = data);
};
ContributorsGraph.set_y_domain = function(data) {
- return ContributorsGraph.prototype.y_domain = [
- 0, d3.max(data, function(d) {
- return d.commits = d.commits || d.additions || d.deletions;
- })
- ];
+ return (ContributorsGraph.prototype.y_domain = [
+ 0,
+ d3.max(data, function(d) {
+ return (d.commits = d.commits || d.additions || d.deletions);
+ }),
+ ]);
};
ContributorsGraph.init_x_domain = function(data) {
- return ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
+ return (ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) {
return d.date;
- });
+ }));
};
ContributorsGraph.init_y_domain = function(data) {
- return ContributorsGraph.prototype.y_domain = [
- 0, d3.max(data, function(d) {
- return d.commits = d.commits || d.additions || d.deletions;
- })
- ];
+ return (ContributorsGraph.prototype.y_domain = [
+ 0,
+ d3.max(data, function(d) {
+ return (d.commits = d.commits || d.additions || d.deletions);
+ }),
+ ]);
};
ContributorsGraph.init_domain = function(data) {
@@ -70,7 +96,7 @@ export const ContributorsGraph = (function() {
};
ContributorsGraph.set_dates = function(data) {
- return ContributorsGraph.prototype.dates = data;
+ return (ContributorsGraph.prototype.dates = data);
};
ContributorsGraph.prototype.set_x_domain = function() {
@@ -87,20 +113,33 @@ export const ContributorsGraph = (function() {
};
ContributorsGraph.prototype.create_scale = function(width, height) {
- this.x = d3.scaleTime().range([0, width]).clamp(true);
- return this.y = d3.scaleLinear().range([height, 0]).nice();
+ this.x = d3
+ .scaleTime()
+ .range([0, width])
+ .clamp(true);
+ return (this.y = d3
+ .scaleLinear()
+ .range([height, 0])
+ .nice());
};
ContributorsGraph.prototype.draw_x_axis = function() {
- return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0, " + this.height + ")").call(this.x_axis);
+ return this.svg
+ .append('g')
+ .attr('class', 'x axis')
+ .attr('transform', 'translate(0, ' + this.height + ')')
+ .call(this.x_axis);
};
ContributorsGraph.prototype.draw_y_axis = function() {
- return this.svg.append("g").attr("class", "y axis").call(this.y_axis);
+ return this.svg
+ .append('g')
+ .attr('class', 'y axis')
+ .call(this.y_axis);
};
ContributorsGraph.prototype.set_data = function(data) {
- return this.data = data;
+ return (this.data = data);
};
return ContributorsGraph;
@@ -137,9 +176,9 @@ export const ContributorsMasterGraph = (function(superClass) {
};
ContributorsMasterGraph.prototype.parse_dates = function(data) {
- const parseDate = d3.timeParse("%Y-%m-%d");
+ const parseDate = d3.timeParse('%Y-%m-%d');
return data.forEach(function(d) {
- return d.date = parseDate(d.date);
+ return (d.date = parseDate(d.date));
});
};
@@ -148,42 +187,63 @@ export const ContributorsMasterGraph = (function(superClass) {
};
ContributorsMasterGraph.prototype.create_axes = function() {
- this.x_axis = d3.axisBottom()
+ this.x_axis = d3
+ .axisBottom()
.scale(this.x)
.tickFormat(dateTickFormat);
- return this.y_axis = d3.axisLeft().scale(this.y).ticks(5);
+ return (this.y_axis = d3
+ .axisLeft()
+ .scale(this.y)
+ .ticks(5));
};
ContributorsMasterGraph.prototype.create_svg = function() {
- this.svg = d3.select("#contributors-master")
- .append("svg")
- .attr("width", this.width + this.MARGIN.left + this.MARGIN.right)
- .attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom)
- .attr("class", "tint-box")
- .append("g")
- .attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+ this.svg = d3
+ .select('#contributors-master')
+ .append('svg')
+ .attr('width', this.width + this.MARGIN.left + this.MARGIN.right)
+ .attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom)
+ .attr('class', 'tint-box')
+ .append('g')
+ .attr('transform', 'translate(' + this.MARGIN.left + ',' + this.MARGIN.top + ')');
return this.svg;
};
ContributorsMasterGraph.prototype.create_area = function(x, y) {
- return this.area = d3.area().x(function(d) {
- return x(d.date);
- }).y0(this.height).y1(function(d) {
- d.commits = d.commits || d.additions || d.deletions;
- return y(d.commits);
- });
+ return (this.area = d3
+ .area()
+ .x(function(d) {
+ return x(d.date);
+ })
+ .y0(this.height)
+ .y1(function(d) {
+ d.commits = d.commits || d.additions || d.deletions;
+ return y(d.commits);
+ }));
};
ContributorsMasterGraph.prototype.create_brush = function() {
- return this.brush = d3.brushX(this.x).extent([[this.x.range()[0], 0], [this.x.range()[1], this.height]]).on("end", this.update_content);
+ return (this.brush = d3
+ .brushX(this.x)
+ .extent([[this.x.range()[0], 0], [this.x.range()[1], this.height]])
+ .on('end', this.update_content));
};
ContributorsMasterGraph.prototype.draw_path = function(data) {
- return this.svg.append("path").datum(data).attr("class", "area").attr("d", this.area);
+ return this.svg
+ .append('path')
+ .datum(data)
+ .attr('class', 'area')
+ .attr('d', this.area);
};
ContributorsMasterGraph.prototype.add_brush = function() {
- return this.svg.append("g").attr("class", "selection").call(this.brush).selectAll("rect").attr("height", this.height);
+ return this.svg
+ .append('g')
+ .attr('class', 'selection')
+ .call(this.brush)
+ .selectAll('rect')
+ .attr('height', this.height);
};
ContributorsMasterGraph.prototype.update_content = function() {
@@ -193,7 +253,7 @@ export const ContributorsMasterGraph = (function(superClass) {
} else {
ContributorsGraph.set_x_domain(this.x_max_domain);
}
- return $("#brush_change").trigger('change');
+ return $('#brush_change').trigger('change');
};
ContributorsMasterGraph.prototype.draw = function() {
@@ -216,9 +276,9 @@ export const ContributorsMasterGraph = (function(superClass) {
this.process_dates(this.data);
ContributorsGraph.set_y_domain(this.data);
this.set_y_domain();
- this.svg.select("path").datum(this.data);
- this.svg.select("path").attr("d", this.area);
- return this.svg.select(".y.axis").call(this.y_axis);
+ this.svg.select('path').datum(this.data);
+ this.svg.select('path').attr('d', this.area);
+ return this.svg.select('.y.axis').call(this.y_axis);
};
return ContributorsMasterGraph;
@@ -252,43 +312,58 @@ export const ContributorsAuthorGraph = (function(superClass) {
};
ContributorsAuthorGraph.prototype.create_axes = function() {
- this.x_axis = d3.axisBottom()
+ this.x_axis = d3
+ .axisBottom()
.scale(this.x)
.ticks(8)
.tickFormat(dateTickFormat);
- return this.y_axis = d3.axisLeft().scale(this.y).ticks(5);
+ return (this.y_axis = d3
+ .axisLeft()
+ .scale(this.y)
+ .ticks(5));
};
ContributorsAuthorGraph.prototype.create_area = function(x, y) {
- return this.area = d3.area().x(function(d) {
- const parseDate = d3.timeParse("%Y-%m-%d");
- return x(parseDate(d));
- }).y0(this.height).y1((function(_this) {
- return function(d) {
- if (_this.data[d] != null) {
- return y(_this.data[d]);
- } else {
- return y(0);
- }
- };
- })(this));
+ return (this.area = d3
+ .area()
+ .x(function(d) {
+ const parseDate = d3.timeParse('%Y-%m-%d');
+ return x(parseDate(d));
+ })
+ .y0(this.height)
+ .y1(
+ (function(_this) {
+ return function(d) {
+ if (_this.data[d] != null) {
+ return y(_this.data[d]);
+ } else {
+ return y(0);
+ }
+ };
+ })(this),
+ ));
};
ContributorsAuthorGraph.prototype.create_svg = function() {
const persons = document.querySelectorAll('.person');
this.list_item = persons[persons.length - 1];
- this.svg = d3.select(this.list_item)
- .append("svg")
- .attr("width", this.width + this.MARGIN.left + this.MARGIN.right)
- .attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom)
- .attr("class", "spark")
- .append("g")
- .attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
+ this.svg = d3
+ .select(this.list_item)
+ .append('svg')
+ .attr('width', this.width + this.MARGIN.left + this.MARGIN.right)
+ .attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom)
+ .attr('class', 'spark')
+ .append('g')
+ .attr('transform', 'translate(' + this.MARGIN.left + ',' + this.MARGIN.top + ')');
return this.svg;
};
ContributorsAuthorGraph.prototype.draw_path = function(data) {
- return this.svg.append("path").datum(data).attr("class", "area-contributor").attr("d", this.area);
+ return this.svg
+ .append('path')
+ .datum(data)
+ .attr('class', 'area-contributor')
+ .attr('d', this.area);
};
ContributorsAuthorGraph.prototype.draw = function() {
@@ -304,10 +379,10 @@ export const ContributorsAuthorGraph = (function(superClass) {
ContributorsAuthorGraph.prototype.redraw = function() {
this.set_domain();
- this.svg.select("path").datum(this.dates);
- this.svg.select("path").attr("d", this.area);
- this.svg.select(".x.axis").call(this.x_axis);
- return this.svg.select(".y.axis").call(this.y_axis);
+ this.svg.select('path').datum(this.dates);
+ this.svg.select('path').attr('d', this.area);
+ this.svg.select('.x.axis').call(this.x_axis);
+ return this.svg.select('.y.axis').call(this.y_axis);
};
return ContributorsAuthorGraph;
diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
index cd0e2bc023c..988ae164955 100644
--- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
+++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js
@@ -26,12 +26,12 @@ export default {
by_author = _.toArray(by_author);
return {
total: total,
- by_author: by_author
+ by_author: by_author,
};
},
add_date: function(date, collection) {
collection[date] = {};
- return collection[date].date = date;
+ return (collection[date].date = date);
},
add_author: function(author, by_author, by_email) {
var data, normalized_email;
@@ -49,28 +49,28 @@ export default {
return this.store_deletions(entry, total, by_author);
},
store_commits: function(total, by_author) {
- this.add(total, "commits", 1);
- return this.add(by_author, "commits", 1);
+ this.add(total, 'commits', 1);
+ return this.add(by_author, 'commits', 1);
},
add: function(collection, field, value) {
if (collection[field] == null) {
collection[field] = 0;
}
- return collection[field] += value;
+ return (collection[field] += value);
},
store_additions: function(entry, total, by_author) {
if (entry.additions == null) {
entry.additions = 0;
}
- this.add(total, "additions", entry.additions);
- return this.add(by_author, "additions", entry.additions);
+ this.add(total, 'additions', entry.additions);
+ return this.add(by_author, 'additions', entry.additions);
},
store_deletions: function(entry, total, by_author) {
if (entry.deletions == null) {
entry.deletions = 0;
}
- this.add(total, "deletions", entry.deletions);
- return this.add(by_author, "deletions", entry.deletions);
+ this.add(total, 'deletions', entry.deletions);
+ return this.add(by_author, 'deletions', entry.deletions);
},
get_total_data: function(parsed_log, field) {
var log, total_data;
@@ -95,15 +95,18 @@ export default {
}
log = parsed_log.by_author;
author_data = [];
- _.each(log, (function(_this) {
- return function(log_entry) {
- var parsed_log_entry;
- parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
- if (!_.isEmpty(parsed_log_entry.dates)) {
- return author_data.push(parsed_log_entry);
- }
- };
- })(this));
+ _.each(
+ log,
+ (function(_this) {
+ return function(log_entry) {
+ var parsed_log_entry;
+ parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range);
+ if (!_.isEmpty(parsed_log_entry.dates)) {
+ return author_data.push(parsed_log_entry);
+ }
+ };
+ })(this),
+ );
return _.sortBy(author_data, function(d) {
return d[field];
}).reverse();
@@ -120,16 +123,19 @@ export default {
parsed_entry.additions = 0;
parsed_entry.deletions = 0;
- _.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) {
- return function(value, key) {
- if (_this.in_range(value.date, date_range)) {
- parsed_entry.dates[value.date] = value[field];
- parsed_entry.commits += value.commits;
- parsed_entry.additions += value.additions;
- return parsed_entry.deletions += value.deletions;
- }
- };
- })(this));
+ _.each(
+ _.omit(log_entry, 'author_name', 'author_email'),
+ (function(_this) {
+ return function(value, key) {
+ if (_this.in_range(value.date, date_range)) {
+ parsed_entry.dates[value.date] = value[field];
+ parsed_entry.commits += value.commits;
+ parsed_entry.additions += value.additions;
+ return (parsed_entry.deletions += value.deletions);
+ }
+ };
+ })(this),
+ );
return parsed_entry;
},
in_range: function(date, date_range) {
@@ -139,5 +145,5 @@ export default {
} else {
return false;
}
- }
+ },
};
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
index bc08ccf3584..bd8afa2d5ba 100644
--- a/app/assets/javascripts/pages/projects/init_blob.js
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -16,7 +16,8 @@ export default () => {
);
const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
- const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
+ const fileBlobPermalinkUrl =
+ fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsNavigation(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/init_form.js b/app/assets/javascripts/pages/projects/init_form.js
index 9f20a3e4e46..019efe077f7 100644
--- a/app/assets/javascripts/pages/projects/init_form.js
+++ b/app/assets/javascripts/pages/projects/init_form.js
@@ -1,7 +1,7 @@
import ZenMode from '~/zen_mode';
import GLForm from '~/gl_form';
-export default function ($formEl) {
+export default function($formEl) {
new ZenMode(); // eslint-disable-line no-new
new GLForm($formEl); // eslint-disable-line no-new
}
diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js
index ef65196872c..8987c8e3f47 100644
--- a/app/assets/javascripts/pages/projects/issues/show.js
+++ b/app/assets/javascripts/pages/projects/issues/show.js
@@ -5,7 +5,7 @@ import ZenMode from '~/zen_mode';
import '~/notes/index';
import initIssueableApp from '~/issue_show';
-export default function () {
+export default function() {
initIssueableApp();
new Issue(); // eslint-disable-line no-new
new ShortcutsIssuable(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/jobs/show/index.js b/app/assets/javascripts/pages/projects/jobs/show/index.js
index 3626f3ffec6..d57dbeb1242 100644
--- a/app/assets/javascripts/pages/projects/jobs/show/index.js
+++ b/app/assets/javascripts/pages/projects/jobs/show/index.js
@@ -1,3 +1,3 @@
-import initJobDetails from '~/jobs/job_details_bundle';
+import initJobDetails from '~/jobs';
document.addEventListener('DOMContentLoaded', initJobDetails);
diff --git a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
index 5d2247f6c6d..e8b646f3f6e 100644
--- a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
+++ b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue
@@ -1,72 +1,86 @@
<script>
- import _ from 'underscore';
- import axios from '~/lib/utils/axios_utils';
- import createFlash from '~/flash';
- import GlModal from '~/vue_shared/components/gl_modal.vue';
- import { s__, sprintf } from '~/locale';
- import { visitUrl } from '~/lib/utils/url_utility';
- import eventHub from '../event_hub';
+import _ from 'underscore';
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
+import GlModal from '~/vue_shared/components/gl_modal.vue';
+import { s__, sprintf } from '~/locale';
+import { visitUrl } from '~/lib/utils/url_utility';
+import eventHub from '../event_hub';
- export default {
- components: {
- GlModal,
+export default {
+ components: {
+ GlModal,
+ },
+ props: {
+ url: {
+ type: String,
+ required: true,
},
- props: {
- url: {
- type: String,
- required: true,
- },
- labelTitle: {
- type: String,
- required: true,
- },
- labelColor: {
- type: String,
- required: true,
- },
- labelTextColor: {
- type: String,
- required: true,
- },
- groupName: {
- type: String,
- required: true,
- },
+ labelTitle: {
+ type: String,
+ required: true,
},
- computed: {
- text() {
- return sprintf(s__(`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.`), {
+ labelColor: {
+ type: String,
+ required: true,
+ },
+ labelTextColor: {
+ type: String,
+ required: true,
+ },
+ groupName: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ text() {
+ return sprintf(
+ s__(`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.`),
+ {
labelTitle: this.labelTitle,
groupName: this.groupName,
- });
- },
- title() {
- const label = `<span
+ },
+ );
+ },
+ title() {
+ const label = `<span
class="label color-label"
style="background-color: ${this.labelColor}; color: ${this.labelTextColor};"
>${_.escape(this.labelTitle)}</span>`;
- return sprintf(s__('Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>'), {
+ return sprintf(
+ s__('Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>'),
+ {
labelTitle: label,
- }, false);
- },
+ },
+ false,
+ );
},
- methods: {
- onSubmit() {
- eventHub.$emit('promoteLabelModal.requestStarted', this.url);
- return axios.post(this.url, { params: { format: 'json' } })
- .then((response) => {
- eventHub.$emit('promoteLabelModal.requestFinished', { labelUrl: this.url, successful: true });
- visitUrl(response.data.url);
- })
- .catch((error) => {
- eventHub.$emit('promoteLabelModal.requestFinished', { labelUrl: this.url, successful: false });
- createFlash(error);
+ },
+ methods: {
+ onSubmit() {
+ eventHub.$emit('promoteLabelModal.requestStarted', this.url);
+ return axios
+ .post(this.url, { params: { format: 'json' } })
+ .then(response => {
+ eventHub.$emit('promoteLabelModal.requestFinished', {
+ labelUrl: this.url,
+ successful: true,
+ });
+ visitUrl(response.data.url);
+ })
+ .catch(error => {
+ eventHub.$emit('promoteLabelModal.requestFinished', {
+ labelUrl: this.url,
+ successful: false,
});
- },
+ createFlash(error);
+ });
},
- };
+ },
+};
</script>
<template>
<gl-modal
diff --git a/app/assets/javascripts/pages/projects/labels/index/index.js b/app/assets/javascripts/pages/projects/labels/index/index.js
index 03cfef61311..36cf485f33d 100644
--- a/app/assets/javascripts/pages/projects/labels/index/index.js
+++ b/app/assets/javascripts/pages/projects/labels/index/index.js
@@ -10,20 +10,24 @@ const initLabelIndex = () => {
initLabels();
const onRequestFinished = ({ labelUrl, successful }) => {
- const button = document.querySelector(`.js-promote-project-label-button[data-url="${labelUrl}"]`);
+ const button = document.querySelector(
+ `.js-promote-project-label-button[data-url="${labelUrl}"]`,
+ );
if (!successful) {
button.removeAttribute('disabled');
}
};
- const onRequestStarted = (labelUrl) => {
- const button = document.querySelector(`.js-promote-project-label-button[data-url="${labelUrl}"]`);
+ const onRequestStarted = labelUrl => {
+ const button = document.querySelector(
+ `.js-promote-project-label-button[data-url="${labelUrl}"]`,
+ );
button.setAttribute('disabled', '');
eventHub.$once('promoteLabelModal.requestFinished', onRequestFinished);
};
- const onDeleteButtonClick = (event) => {
+ const onDeleteButtonClick = event => {
const button = event.currentTarget;
const modalProps = {
labelTitle: button.dataset.labelTitle,
@@ -37,12 +41,12 @@ const initLabelIndex = () => {
};
const promoteLabelButtons = document.querySelectorAll('.js-promote-project-label-button');
- promoteLabelButtons.forEach((button) => {
+ promoteLabelButtons.forEach(button => {
button.addEventListener('click', onDeleteButtonClick);
});
eventHub.$once('promoteLabelModal.mounted', () => {
- promoteLabelButtons.forEach((button) => {
+ promoteLabelButtons.forEach(button => {
button.removeAttribute('disabled');
});
});
diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js
index 70fbb3f301c..226d63f05c4 100644
--- a/app/assets/javascripts/pages/projects/network/network.js
+++ b/app/assets/javascripts/pages/projects/network/network.js
@@ -6,13 +6,15 @@ import BranchGraph from '../../../network/branch_graph';
export default (function() {
function Network(opts) {
var vph;
- $("#filter_ref").click(function() {
- return $(this).closest('form').submit();
+ $('#filter_ref').click(function() {
+ return $(this)
+ .closest('form')
+ .submit();
});
- this.branch_graph = new BranchGraph($(".network-graph"), opts);
+ this.branch_graph = new BranchGraph($('.network-graph'), opts);
vph = $(window).height() - 250;
$('.network-graph').css({
- 'height': vph + 'px'
+ height: vph + 'px',
});
}
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
index 544360dcd51..6197dc8a9db 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js
@@ -1,12 +1,16 @@
import Vue from 'vue';
import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
-document.addEventListener('DOMContentLoaded', () => new Vue({
- el: '#pipeline-schedules-callout',
- components: {
- 'pipeline-schedules-callout': PipelineSchedulesCallout,
- },
- render(createElement) {
- return createElement('pipeline-schedules-callout');
- },
-}));
+document.addEventListener(
+ 'DOMContentLoaded',
+ () =>
+ new Vue({
+ el: '#pipeline-schedules-callout',
+ components: {
+ 'pipeline-schedules-callout': PipelineSchedulesCallout,
+ },
+ render(createElement) {
+ return createElement('pipeline-schedules-callout');
+ },
+ }),
+);
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index ef53d67e7cb..ab6f42d928c 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
@@ -1,63 +1,63 @@
<script>
- import _ from 'underscore';
+import _ from 'underscore';
- export default {
- props: {
- initialCronInterval: {
- type: String,
- required: false,
- default: '',
- },
- },
- data() {
- return {
- inputNameAttribute: 'schedule[cron]',
- cronInterval: this.initialCronInterval,
- cronIntervalPresets: {
- everyDay: '0 4 * * *',
- everyWeek: '0 4 * * 0',
- everyMonth: '0 4 1 * *',
- },
- cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron',
- customInputEnabled: false,
- };
+export default {
+ props: {
+ initialCronInterval: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- intervalIsPreset() {
- return _.contains(this.cronIntervalPresets, this.cronInterval);
- },
- // The text input is editable when there's a custom interval, or when it's
- // a preset interval and the user clicks the 'custom' radio button
- isEditable() {
- return !!(this.customInputEnabled || !this.intervalIsPreset);
+ },
+ data() {
+ return {
+ inputNameAttribute: 'schedule[cron]',
+ cronInterval: this.initialCronInterval,
+ cronIntervalPresets: {
+ everyDay: '0 4 * * *',
+ everyWeek: '0 4 * * 0',
+ everyMonth: '0 4 1 * *',
},
+ cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron',
+ customInputEnabled: false,
+ };
+ },
+ computed: {
+ intervalIsPreset() {
+ return _.contains(this.cronIntervalPresets, this.cronInterval);
},
- watch: {
- cronInterval() {
- // updates field validation state when model changes, as
- // glFieldError only updates on input.
- this.$nextTick(() => {
- gl.pipelineScheduleFieldErrors.updateFormValidityState();
- });
- },
+ // The text input is editable when there's a custom interval, or when it's
+ // a preset interval and the user clicks the 'custom' radio button
+ isEditable() {
+ return !!(this.customInputEnabled || !this.intervalIsPreset);
},
- created() {
- if (this.intervalIsPreset) {
- this.enableCustomInput = false;
- }
+ },
+ watch: {
+ cronInterval() {
+ // updates field validation state when model changes, as
+ // glFieldError only updates on input.
+ this.$nextTick(() => {
+ gl.pipelineScheduleFieldErrors.updateFormValidityState();
+ });
},
- methods: {
- toggleCustomInput(shouldEnable) {
- this.customInputEnabled = shouldEnable;
+ },
+ created() {
+ if (this.intervalIsPreset) {
+ this.enableCustomInput = false;
+ }
+ },
+ methods: {
+ toggleCustomInput(shouldEnable) {
+ this.customInputEnabled = shouldEnable;
- if (shouldEnable) {
- // We need to change the value so other radios don't remain selected
- // because the model (cronInterval) hasn't changed. The server trims it.
- this.cronInterval = `${this.cronInterval} `;
- }
- },
+ if (shouldEnable) {
+ // We need to change the value so other radios don't remain selected
+ // because the model (cronInterval) hasn't changed. The server trims it.
+ this.cronInterval = `${this.cronInterval} `;
+ }
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
index 77508e62cef..33fc2420e4d 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
@@ -1,31 +1,31 @@
<script>
- import Vue from 'vue';
- import Cookies from 'js-cookie';
- import Translate from '../../../../../vue_shared/translate';
- import illustrationSvg from '../icons/intro_illustration.svg';
+import Vue from 'vue';
+import Cookies from 'js-cookie';
+import Translate from '../../../../../vue_shared/translate';
+import illustrationSvg from '../icons/intro_illustration.svg';
- Vue.use(Translate);
+Vue.use(Translate);
- const cookieKey = 'pipeline_schedules_callout_dismissed';
+const cookieKey = 'pipeline_schedules_callout_dismissed';
- export default {
- name: 'PipelineSchedulesCallout',
- data() {
- return {
- docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl,
- calloutDismissed: Cookies.get(cookieKey) === 'true',
- };
+export default {
+ name: 'PipelineSchedulesCallout',
+ data() {
+ return {
+ docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl,
+ calloutDismissed: Cookies.get(cookieKey) === 'true',
+ };
+ },
+ created() {
+ this.illustrationSvg = illustrationSvg;
+ },
+ methods: {
+ dismissCallout() {
+ this.calloutDismissed = true;
+ Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 });
},
- created() {
- this.illustrationSvg = illustrationSvg;
- },
- methods: {
- dismissCallout() {
- this.calloutDismissed = true;
- Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 });
- },
- },
- };
+ },
+};
</script>
<template>
<div
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
index 4ef0d11dd36..0057700c1b3 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
@@ -26,8 +26,7 @@ export default class TargetBranchDropdown {
}
formatBranchesList() {
- return this.$dropdown.data('data')
- .map(val => ({ name: val }));
+ return this.$dropdown.data('data').map(val => ({ name: val }));
}
setDropdownToggle() {
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
index c3ac54733a3..4d494efef6c 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js
@@ -11,7 +11,9 @@ Vue.use(Translate);
function initIntervalPatternInput() {
const intervalPatternMount = document.getElementById('interval-pattern-input');
- const initialCronInterval = intervalPatternMount ? intervalPatternMount.dataset.initialInterval : '';
+ const initialCronInterval = intervalPatternMount
+ ? intervalPatternMount.dataset.initialInterval
+ : '';
return new Vue({
el: intervalPatternMount,
diff --git a/app/assets/javascripts/pages/projects/pipelines/charts/index.js b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
index 07b6992eba1..48353f3b4ef 100644
--- a/app/assets/javascripts/pages/projects/pipelines/charts/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/charts/index.js
@@ -7,26 +7,29 @@ const options = {
maintainAspectRatio: false,
};
-const buildChart = (chartScope) => {
+const buildChart = chartScope => {
const data = {
labels: chartScope.labels,
- datasets: [{
- fillColor: '#707070',
- strokeColor: '#707070',
- pointColor: '#707070',
- pointStrokeColor: '#EEE',
- data: chartScope.totalValues,
- },
- {
- fillColor: '#1aaa55',
- strokeColor: '#1aaa55',
- pointColor: '#1aaa55',
- pointStrokeColor: '#fff',
- data: chartScope.successValues,
- },
+ datasets: [
+ {
+ fillColor: '#707070',
+ strokeColor: '#707070',
+ pointColor: '#707070',
+ pointStrokeColor: '#EEE',
+ data: chartScope.totalValues,
+ },
+ {
+ fillColor: '#1aaa55',
+ strokeColor: '#1aaa55',
+ pointColor: '#1aaa55',
+ pointStrokeColor: '#fff',
+ data: chartScope.successValues,
+ },
],
};
- const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
+ const ctx = $(`#${chartScope.scope}Chart`)
+ .get(0)
+ .getContext('2d');
new Chart(ctx).Line(data, options);
};
@@ -36,14 +39,16 @@ document.addEventListener('DOMContentLoaded', () => {
const chartsData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
const data = {
labels: chartTimesData.labels,
- datasets: [{
- fillColor: 'rgba(220,220,220,0.5)',
- strokeColor: 'rgba(220,220,220,1)',
- barStrokeWidth: 1,
- barValueSpacing: 1,
- barDatasetSpacing: 1,
- data: chartTimesData.values,
- }],
+ datasets: [
+ {
+ fillColor: 'rgba(220,220,220,0.5)',
+ strokeColor: 'rgba(220,220,220,1)',
+ barStrokeWidth: 1,
+ barValueSpacing: 1,
+ barDatasetSpacing: 1,
+ data: chartTimesData.values,
+ },
+ ],
};
if (window.innerWidth < 768) {
@@ -51,7 +56,11 @@ document.addEventListener('DOMContentLoaded', () => {
options.scaleFontSize = 8;
}
- new Chart($('#build_timesChart').get(0).getContext('2d')).Bar(data, options);
+ new Chart(
+ $('#build_timesChart')
+ .get(0)
+ .getContext('2d'),
+ ).Bar(data, options);
chartsData.forEach(scope => buildChart(scope));
});
diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js
index a84e2790680..fc337a7609b 100644
--- a/app/assets/javascripts/pages/projects/pipelines/index/index.js
+++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js
@@ -6,35 +6,39 @@ import { convertPermissionToBoolean } from '../../../../lib/utils/common_utils';
Vue.use(Translate);
-document.addEventListener('DOMContentLoaded', () => new Vue({
- el: '#pipelines-list-vue',
- components: {
- pipelinesComponent,
- },
- data() {
- return {
- store: new PipelinesStore(),
- };
- },
- created() {
- this.dataset = document.querySelector(this.$options.el).dataset;
- },
- render(createElement) {
- return createElement('pipelines-component', {
- props: {
- store: this.store,
- endpoint: this.dataset.endpoint,
- helpPagePath: this.dataset.helpPagePath,
- emptyStateSvgPath: this.dataset.emptyStateSvgPath,
- errorStateSvgPath: this.dataset.errorStateSvgPath,
- noPipelinesSvgPath: this.dataset.noPipelinesSvgPath,
- autoDevopsPath: this.dataset.helpAutoDevopsPath,
- newPipelinePath: this.dataset.newPipelinePath,
- canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline),
- hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi),
- ciLintPath: this.dataset.ciLintPath,
- resetCachePath: this.dataset.resetCachePath,
+document.addEventListener(
+ 'DOMContentLoaded',
+ () =>
+ new Vue({
+ el: '#pipelines-list-vue',
+ components: {
+ pipelinesComponent,
},
- });
- },
-}));
+ data() {
+ return {
+ store: new PipelinesStore(),
+ };
+ },
+ created() {
+ this.dataset = document.querySelector(this.$options.el).dataset;
+ },
+ render(createElement) {
+ return createElement('pipelines-component', {
+ props: {
+ store: this.store,
+ endpoint: this.dataset.endpoint,
+ helpPagePath: this.dataset.helpPagePath,
+ emptyStateSvgPath: this.dataset.emptyStateSvgPath,
+ errorStateSvgPath: this.dataset.errorStateSvgPath,
+ noPipelinesSvgPath: this.dataset.noPipelinesSvgPath,
+ autoDevopsPath: this.dataset.helpAutoDevopsPath,
+ newPipelinePath: this.dataset.newPipelinePath,
+ canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline),
+ hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi),
+ ciLintPath: this.dataset.ciLintPath,
+ resetCachePath: this.dataset.resetCachePath,
+ },
+ });
+ },
+ }),
+);
diff --git a/app/assets/javascripts/pages/projects/pipelines/init_pipelines.js b/app/assets/javascripts/pages/projects/pipelines/init_pipelines.js
index 94dfeb96e8c..ba4ae04ab3d 100644
--- a/app/assets/javascripts/pages/projects/pipelines/init_pipelines.js
+++ b/app/assets/javascripts/pages/projects/pipelines/init_pipelines.js
@@ -2,9 +2,12 @@ import Pipelines from '~/pipelines';
export default () => {
const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
- const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`;
+ const pipelineStatusUrl = `${document
+ .querySelector('.js-pipeline-tab-link a')
+ .getAttribute('href')}/status.json`;
- new Pipelines({ // eslint-disable-line no-new
+ // eslint-disable-next-line no-new
+ new Pipelines({
initTabs: true,
pipelineStatusUrl,
tabsOptions: {
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
index 06101290f6c..dced839c883 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue
@@ -1,73 +1,71 @@
<script>
- import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
+import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue';
- export default {
- components: {
- projectFeatureToggle,
- },
+export default {
+ components: {
+ projectFeatureToggle,
+ },
- model: {
- prop: 'value',
- event: 'change',
- },
+ model: {
+ prop: 'value',
+ event: 'change',
+ },
- props: {
- name: {
- type: String,
- required: false,
- default: '',
- },
- options: {
- type: Array,
- required: false,
- default: () => [],
- },
- value: {
- type: Number,
- required: false,
- default: 0,
- },
- disabledInput: {
- type: Boolean,
- required: false,
- default: false,
- },
+ props: {
+ name: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ options: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ value: {
+ type: Number,
+ required: false,
+ default: 0,
},
+ disabledInput: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
- computed: {
- featureEnabled() {
- return this.value !== 0;
- },
+ computed: {
+ featureEnabled() {
+ return this.value !== 0;
+ },
- displayOptions() {
- if (this.featureEnabled) {
- return this.options;
- }
- return [
- [0, 'Enable feature to choose access level'],
- ];
- },
+ displayOptions() {
+ if (this.featureEnabled) {
+ return this.options;
+ }
+ return [[0, 'Enable feature to choose access level']];
+ },
- displaySelectInput() {
- return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2;
- },
+ displaySelectInput() {
+ return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2;
},
+ },
- methods: {
- toggleFeature(featureEnabled) {
- if (featureEnabled === false || this.options.length < 1) {
- this.$emit('change', 0);
- } else {
- const [firstOptionValue] = this.options[this.options.length - 1];
- this.$emit('change', firstOptionValue);
- }
- },
+ methods: {
+ toggleFeature(featureEnabled) {
+ if (featureEnabled === false || this.options.length < 1) {
+ this.$emit('change', 0);
+ } else {
+ const [firstOptionValue] = this.options[this.options.length - 1];
+ this.$emit('change', firstOptionValue);
+ }
+ },
- selectOption(e) {
- this.$emit('change', Number(e.target.value));
- },
+ selectOption(e) {
+ this.$emit('change', Number(e.target.value));
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
index 83437363af5..898d605463f 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue
@@ -1,23 +1,23 @@
<script>
- export default {
- props: {
- label: {
- type: String,
- required: false,
- default: null,
- },
- helpPath: {
- type: String,
- required: false,
- default: null,
- },
- helpText: {
- type: String,
- required: false,
- default: null,
- },
+export default {
+ props: {
+ label: {
+ type: String,
+ required: false,
+ default: null,
},
- };
+ helpPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ helpText: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/constants.js b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
index ce47562f259..bc5c29d12b5 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/constants.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/constants.js
@@ -5,7 +5,9 @@ export const visibilityOptions = {
};
export const visibilityLevelDescriptions = {
- [visibilityOptions.PRIVATE]: 'The project is accessible only by members of the project. Access must be granted explicitly to each user.',
+ [visibilityOptions.PRIVATE]:
+ 'The project is accessible only by members of the project. Access must be granted explicitly to each user.',
[visibilityOptions.INTERNAL]: 'The project can be accessed by any user who is logged in.',
- [visibilityOptions.PUBLIC]: 'The project can be accessed by anyone, regardless of authentication.',
+ [visibilityOptions.PUBLIC]:
+ 'The project can be accessed by anyone, regardless of authentication.',
};
diff --git a/app/assets/javascripts/pages/projects/shared/project_avatar.js b/app/assets/javascripts/pages/projects/shared/project_avatar.js
index 447877752fe..1e69ecb481d 100644
--- a/app/assets/javascripts/pages/projects/shared/project_avatar.js
+++ b/app/assets/javascripts/pages/projects/shared/project_avatar.js
@@ -8,8 +8,9 @@ export default function projectAvatar() {
$('.js-project-avatar-input').bind('change', function onClickAvatarInput() {
const form = $(this).closest('form');
- // eslint-disable-next-line no-useless-escape
- const filename = $(this).val().replace(/^.*[\\\/]/, '');
+ const filename = $(this)
+ .val()
+ .replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape
return form.find('.js-avatar-filename').text(filename);
});
}
diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js
index c2629090f01..f5fd84d69ac 100644
--- a/app/assets/javascripts/pages/projects/wikis/index.js
+++ b/app/assets/javascripts/pages/projects/wikis/index.js
@@ -21,7 +21,8 @@ document.addEventListener('DOMContentLoaded', () => {
const { deleteWikiUrl, pageTitle } = deleteWikiModalWrapperEl.dataset;
- new Vue({ // eslint-disable-line no-new
+ // eslint-disable-next-line no-new
+ new Vue({
el: deleteWikiModalWrapperEl,
data: {
deleteWikiUrl: '',
diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js
index e3e0ab91993..0c896c8599e 100644
--- a/app/assets/javascripts/pages/search/show/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -22,7 +22,7 @@ export default class Search {
fields: ['full_name'],
},
data(term, callback) {
- return Api.groups(term, {}, (data) => {
+ return Api.groups(term, {}, data => {
data.unshift({
full_name: 'Any',
});
@@ -37,7 +37,7 @@ export default class Search {
return obj.full_name;
},
toggleLabel(obj) {
- return `${($groupDropdown.data('defaultLabel'))} ${obj.full_name}`;
+ return `${$groupDropdown.data('defaultLabel')} ${obj.full_name}`;
},
clicked: () => Search.submitSearch(),
});
@@ -52,7 +52,7 @@ export default class Search {
},
data: (term, callback) => {
this.getProjectsData(term)
- .then((data) => {
+ .then(data => {
data.unshift({
name_with_namespace: 'Any',
});
@@ -70,7 +70,7 @@ export default class Search {
return obj.name_with_namespace;
},
toggleLabel(obj) {
- return `${($projectDropdown.data('defaultLabel'))} ${obj.name_with_namespace}`;
+ return `${$projectDropdown.data('defaultLabel')} ${obj.name_with_namespace}`;
},
clicked: () => Search.submitSearch(),
});
@@ -99,17 +99,24 @@ export default class Search {
}
clearSearchField() {
- return $(this.searchInput).val('').trigger('keyup').focus();
+ return $(this.searchInput)
+ .val('')
+ .trigger('keyup')
+ .focus();
}
getProjectsData(term) {
- return new Promise((resolve) => {
+ return new Promise(resolve => {
if (this.groupId) {
Api.groupProjects(this.groupId, term, {}, resolve);
} else {
- Api.projects(term, {
- order_by: 'id',
- }, resolve);
+ Api.projects(
+ term,
+ {
+ order_by: 'id',
+ },
+ resolve,
+ );
}
});
}
diff --git a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
index 1e7c29aefaa..2b8f1e8b0ef 100644
--- a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
+++ b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js
@@ -20,7 +20,7 @@ export default class SigninTabsMemoizer {
bootstrap() {
const tabs = document.querySelectorAll(this.tabSelector);
if (tabs.length > 0) {
- tabs[0].addEventListener('click', (e) => {
+ tabs[0].addEventListener('click', e => {
if (e.target && e.target.nodeName === 'A') {
const anchorName = e.target.getAttribute('href');
this.saveData(anchorName);
diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js
index d621f988d86..7a41805bada 100644
--- a/app/assets/javascripts/pages/sessions/new/username_validator.js
+++ b/app/assets/javascripts/pages/sessions/new/username_validator.js
@@ -22,10 +22,10 @@ export default class UsernameValidator {
available: false,
valid: false,
pending: false,
- empty: true
+ empty: true,
};
- const debounceTimeout = _.debounce((username) => {
+ const debounceTimeout = _.debounce(username => {
this.validateUsername(username);
}, debounceTimeoutDuration);
@@ -81,7 +81,8 @@ export default class UsernameValidator {
this.state.pending = true;
this.state.available = false;
this.renderState();
- axios.get(`${gon.relative_url_root}/users/${username}/exists`)
+ axios
+ .get(`${gon.relative_url_root}/users/${username}/exists`)
.then(({ data }) => this.setAvailabilityState(data.exists))
.catch(() => flash(__('An error occurred while validating username')));
}
@@ -100,8 +101,7 @@ export default class UsernameValidator {
clearFieldValidationState() {
this.inputElement.siblings('p').hide();
- this.inputElement.removeClass(invalidInputClass)
- .removeClass(successInputClass);
+ this.inputElement.removeClass(invalidInputClass).removeClass(successInputClass);
}
setUnavailableState() {
diff --git a/app/assets/javascripts/pages/users/index.js b/app/assets/javascripts/pages/users/index.js
index 6b1626b0161..a191df00dfa 100644
--- a/app/assets/javascripts/pages/users/index.js
+++ b/app/assets/javascripts/pages/users/index.js
@@ -13,10 +13,12 @@ function initUserProfile(action) {
new UserTabs({ parentEl: '.user-profile', action });
// hide project limit message
- $('.hide-project-limit-message').on('click', (e) => {
+ $('.hide-project-limit-message').on('click', e => {
e.preventDefault();
Cookies.set('hide_project_limit_message', 'false');
- $(this).parents('.project-limit-message').remove();
+ $(this)
+ .parents('.project-limit-message')
+ .remove();
});
}
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 1de9945baad..04bcb16f036 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -170,7 +170,7 @@ export default class UserTabs {
this.loadActivityCalendar('activity');
// eslint-disable-next-line no-new
- new Activities();
+ new Activities('#activity');
this.loaded.activity = true;
}
diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue
index 2f480ecdc69..7b079fe02d9 100644
--- a/app/assets/javascripts/pdf/index.vue
+++ b/app/assets/javascripts/pdf/index.vue
@@ -1,57 +1,58 @@
<script>
- import pdfjsLib from 'vendor/pdf';
- import workerSrc from 'vendor/pdf.worker.min';
+import pdfjsLib from 'vendor/pdf';
+import workerSrc from 'vendor/pdf.worker.min';
- import page from './page/index.vue';
+import page from './page/index.vue';
- export default {
- components: { page },
- props: {
- pdf: {
- type: [String, Uint8Array],
- required: true,
- },
+export default {
+ components: { page },
+ props: {
+ pdf: {
+ type: [String, Uint8Array],
+ required: true,
},
- data() {
- return {
- loading: false,
- pages: [],
- };
+ },
+ data() {
+ return {
+ loading: false,
+ pages: [],
+ };
+ },
+ computed: {
+ document() {
+ return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
},
- computed: {
- document() {
- return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
- },
- hasPDF() {
- return this.pdf && this.pdf.length > 0;
- },
+ hasPDF() {
+ return this.pdf && this.pdf.length > 0;
},
- watch: { pdf: 'load' },
- mounted() {
- pdfjsLib.PDFJS.workerSrc = workerSrc;
- if (this.hasPDF) this.load();
+ },
+ watch: { pdf: 'load' },
+ mounted() {
+ pdfjsLib.PDFJS.workerSrc = workerSrc;
+ if (this.hasPDF) this.load();
+ },
+ methods: {
+ load() {
+ this.pages = [];
+ return pdfjsLib
+ .getDocument(this.document)
+ .then(this.renderPages)
+ .then(() => this.$emit('pdflabload'))
+ .catch(error => this.$emit('pdflaberror', error))
+ .then(() => {
+ this.loading = false;
+ });
},
- methods: {
- load() {
- this.pages = [];
- return pdfjsLib.getDocument(this.document)
- .then(this.renderPages)
- .then(() => this.$emit('pdflabload'))
- .catch(error => this.$emit('pdflaberror', error))
- .then(() => { this.loading = false; });
- },
- renderPages(pdf) {
- const pagePromises = [];
- this.loading = true;
- for (let num = 1; num <= pdf.numPages; num += 1) {
- pagePromises.push(
- pdf.getPage(num).then(p => this.pages.push(p)),
- );
- }
- return Promise.all(pagePromises);
- },
+ renderPages(pdf) {
+ const pagePromises = [];
+ this.loading = true;
+ for (let num = 1; num <= pdf.numPages; num += 1) {
+ pagePromises.push(pdf.getPage(num).then(p => this.pages.push(p)));
+ }
+ return Promise.all(pagePromises);
},
- };
+ },
+};
</script>
<template>
@@ -69,9 +70,9 @@
</template>
<style>
- .pdf-viewer {
- background: url('./assets/img/bg.gif');
- display: flex;
- flex-flow: column nowrap;
- }
+.pdf-viewer {
+ background: url('./assets/img/bg.gif');
+ display: flex;
+ flex-flow: column nowrap;
+}
</style>
diff --git a/app/assets/javascripts/pdf/page/index.vue b/app/assets/javascripts/pdf/page/index.vue
index 9f06833d560..96aadf41653 100644
--- a/app/assets/javascripts/pdf/page/index.vue
+++ b/app/assets/javascripts/pdf/page/index.vue
@@ -1,44 +1,47 @@
<script>
- export default {
- props: {
- page: {
- type: Object,
- required: true,
- },
- number: {
- type: Number,
- required: true,
- },
+export default {
+ props: {
+ page: {
+ type: Object,
+ required: true,
},
- data() {
- return {
- scale: 4,
- rendering: false,
- };
+ number: {
+ type: Number,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ scale: 4,
+ rendering: false,
+ };
+ },
+ computed: {
+ viewport() {
+ return this.page.getViewport(this.scale);
},
- computed: {
- viewport() {
- return this.page.getViewport(this.scale);
- },
- context() {
- return this.$refs.canvas.getContext('2d');
- },
- renderContext() {
- return {
- canvasContext: this.context,
- viewport: this.viewport,
- };
- },
+ context() {
+ return this.$refs.canvas.getContext('2d');
},
- mounted() {
- this.$refs.canvas.height = this.viewport.height;
- this.$refs.canvas.width = this.viewport.width;
- this.rendering = true;
- this.page.render(this.renderContext)
- .then(() => { this.rendering = false; })
- .catch(error => this.$emit('pdflaberror', error));
+ renderContext() {
+ return {
+ canvasContext: this.context,
+ viewport: this.viewport,
+ };
},
- };
+ },
+ mounted() {
+ this.$refs.canvas.height = this.viewport.height;
+ this.$refs.canvas.width = this.viewport.width;
+ this.rendering = true;
+ this.page
+ .render(this.renderContext)
+ .then(() => {
+ this.rendering = false;
+ })
+ .catch(error => this.$emit('pdflaberror', error));
+ },
+};
</script>
<template>
@@ -51,20 +54,20 @@
</template>
<style>
- .pdf-page {
- margin: 8px auto 0 auto;
- border-top: 1px #ddd solid;
- border-bottom: 1px #ddd solid;
- width: 100%;
- }
+.pdf-page {
+ margin: 8px auto 0 auto;
+ border-top: 1px #ddd solid;
+ border-bottom: 1px #ddd solid;
+ width: 100%;
+}
- .pdf-page:first-child {
- margin-top: 0px;
- border-top: 0px;
- }
+.pdf-page:first-child {
+ margin-top: 0px;
+ border-top: 0px;
+}
- .pdf-page:last-child {
- margin-bottom: 0px;
- border-bottom: 0px;
- }
+.pdf-page:last-child {
+ margin-bottom: 0px;
+ border-bottom: 0px;
+}
</style>
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 9b4ba0c1a9a..23c0be7742e 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -70,7 +70,7 @@ export default {
v-for="(stage, index) in graph"
:key="stage.name"
:title="capitalizeStageName(stage.name)"
- :jobs="stage.groups"
+ :groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
:is-first-column="isFirstColumn(index)"
@refreshPipelineGraph="refreshPipelineGraph"
diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
index 2ad66f4fe86..34bada533df 100644
--- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
@@ -1,31 +1,14 @@
<script>
import $ from 'jquery';
-import JobNameComponent from './job_name_component.vue';
-import JobComponent from './job_component.vue';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import JobItem from './job_item.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
/**
* Renders the dropdown for the pipeline graph.
*
- * The following object should be provided as `job`:
+ * The object provided as `group` corresponds to app/serializers/job_group_entity.rb.
*
- * {
- * "id": 4256,
- * "name": "test",
- * "status": {
- * "icon": "status_success",
- * "text": "passed",
- * "label": "passed",
- * "group": "success",
- * "details_path": "/root/ci-mock/builds/4256",
- * "action": {
- * "icon": "retry",
- * "title": "Retry",
- * "path": "/root/ci-mock/builds/4256/retry",
- * "method": "post"
- * }
- * }
- * }
*/
export default {
directives: {
@@ -33,12 +16,12 @@ export default {
},
components: {
- JobComponent,
- JobNameComponent,
+ JobItem,
+ CiIcon,
},
props: {
- job: {
+ group: {
type: Object,
required: true,
},
@@ -46,7 +29,8 @@ export default {
computed: {
tooltipText() {
- return `${this.job.name} - ${this.job.status.label}`;
+ const { name, status } = this.group;
+ return `${name} - ${status.label}`;
},
},
@@ -56,7 +40,7 @@ export default {
methods: {
/**
- * When the user right clicks or cmd/ctrl + click in the job name or the action icon
+ * When the user right clicks or cmd/ctrl + click in the group name or the action icon
* the dropdown should not be closed so we stop propagation
* of the click event inside the dropdown.
*
@@ -90,14 +74,14 @@ export default {
data-display="static"
class="dropdown-menu-toggle build-content"
>
+ <ci-icon :status="group.status" />
- <job-name-component
- :name="job.name"
- :status="job.status"
- />
+ <span class="ci-status-text">
+ {{ group.name }}
+ </span>
<span class="dropdown-counter-badge">
- {{ job.size }}
+ {{ group.size }}
</span>
</button>
@@ -105,12 +89,12 @@ export default {
<li class="scrollable-menu">
<ul>
<li
- v-for="(item, i) in job.jobs"
- :key="i"
+ v-for="job in group.jobs"
+ :key="job.id"
>
- <job-component
- :dropdown-length="job.size"
- :job="item"
+ <job-item
+ :dropdown-length="group.size"
+ :job="job"
css-class-job-name="mini-pipeline-graph-dropdown-item"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue
index a1504592bbc..a1504592bbc 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 567ea119343..efbab51d200 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -1,12 +1,12 @@
<script>
import _ from 'underscore';
-import JobComponent from './job_component.vue';
-import DropdownJobComponent from './dropdown_job_component.vue';
+import JobItem from './job_item.vue';
+import JobGroupDropdown from './job_group_dropdown.vue';
export default {
components: {
- JobComponent,
- DropdownJobComponent,
+ JobItem,
+ JobGroupDropdown,
},
props: {
title: {
@@ -14,7 +14,7 @@ export default {
required: true,
},
- jobs: {
+ groups: {
type: Array,
required: true,
},
@@ -33,12 +33,8 @@ export default {
},
methods: {
- firstJob(list) {
- return list[0];
- },
-
- jobId(job) {
- return `ci-badge-${_.escape(job.name)}`;
+ groupId(group) {
+ return `ci-badge-${_.escape(group.name)}`;
},
buildConnnectorClass(index) {
@@ -61,25 +57,25 @@ export default {
<div class="builds-container">
<ul>
<li
- v-for="(job, index) in jobs"
- :id="jobId(job)"
- :key="job.id"
+ v-for="(group, index) in groups"
+ :id="groupId(group)"
+ :key="group.id"
:class="buildConnnectorClass(index)"
class="build"
>
<div class="curve"></div>
- <job-component
- v-if="job.size === 1"
- :job="job"
+ <job-item
+ v-if="group.size === 1"
+ :job="group.jobs[0]"
css-class-job-name="build-content"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
- <dropdown-job-component
- v-if="job.size > 1"
- :job="job"
+ <job-group-dropdown
+ v-if="group.size > 1"
+ :group="group"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index b03438ddba1..4f89ee66023 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -64,10 +64,7 @@ export default {
return [];
}
const { details } = this.pipeline;
- return [
- ...(details.manual_actions || []),
- ...(details.scheduled_actions || []),
- ];
+ return [...(details.manual_actions || []), ...(details.scheduled_actions || [])];
},
/**
* If provided, returns the commit tag.
diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue
index 47c15b1a9c4..7ec55792850 100644
--- a/app/assets/javascripts/pipelines/components/stage.vue
+++ b/app/assets/javascripts/pipelines/components/stage.vue
@@ -18,14 +18,14 @@ import Flash from '../../flash';
import axios from '../../lib/utils/axios_utils';
import eventHub from '../event_hub';
import Icon from '../../vue_shared/components/icon.vue';
-import JobComponent from './graph/job_component.vue';
+import JobItem from './graph/job_item.vue';
import tooltip from '../../vue_shared/directives/tooltip';
import { PIPELINES_TABLE } from '../constants';
export default {
components: {
Icon,
- JobComponent,
+ JobItem,
},
directives: {
@@ -198,7 +198,7 @@ export default {
v-for="job in dropdownContent"
:key="job.id"
>
- <job-component
+ <job-item
:dropdown-length="dropdownContent.length"
:job="job"
css-class-job-name="mini-pipeline-graph-dropdown-item"
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 12cfa7de316..60d3d83a4b2 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -10,14 +10,14 @@ import { __ } from '~/locale';
const highlighter = function(element, text, matches) {
var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
lastIndex = 0;
- highlightText = "";
+ highlightText = '';
matchedChars = [];
for (j = 0, len = matches.length; j < len; j += 1) {
matchIndex = matches[j];
unmatched = text.substring(lastIndex, matchIndex);
if (unmatched) {
if (matchedChars.length) {
- element.append(matchedChars.join("").bold());
+ element.append(matchedChars.join('').bold());
}
matchedChars = [];
element.append(document.createTextNode(unmatched));
@@ -26,7 +26,7 @@ const highlighter = function(element, text, matches) {
lastIndex = matchIndex + 1;
}
if (matchedChars.length) {
- element.append(matchedChars.join("").bold());
+ element.append(matchedChars.join('').bold());
}
return element.append(document.createTextNode(text.substring(lastIndex)));
};
@@ -40,7 +40,7 @@ export default class ProjectFindFile {
this.selectRowDown = this.selectRowDown.bind(this);
this.selectRowUp = this.selectRowUp.bind(this);
this.filePaths = {};
- this.inputElement = this.element.find(".file-finder-input");
+ this.inputElement = this.element.find('.file-finder-input');
// init event
this.initEvent();
// focus text input box
@@ -50,38 +50,51 @@ export default class ProjectFindFile {
}
initEvent() {
- this.inputElement.off("keyup");
- this.inputElement.on("keyup", (function(_this) {
- return function(event) {
- var oldValue, ref, target, value;
- target = $(event.target);
- value = target.val();
- oldValue = (ref = target.data("oldValue")) != null ? ref : "";
- if (value !== oldValue) {
- target.data("oldValue", value);
- _this.findFile();
- return _this.element.find("tr.tree-item").eq(0).addClass("selected").focus();
- }
- };
- })(this));
+ this.inputElement.off('keyup');
+ this.inputElement.on(
+ 'keyup',
+ (function(_this) {
+ return function(event) {
+ var oldValue, ref, target, value;
+ target = $(event.target);
+ value = target.val();
+ oldValue = (ref = target.data('oldValue')) != null ? ref : '';
+ if (value !== oldValue) {
+ target.data('oldValue', value);
+ _this.findFile();
+ return _this.element
+ .find('tr.tree-item')
+ .eq(0)
+ .addClass('selected')
+ .focus();
+ }
+ };
+ })(this),
+ );
}
findFile() {
var result, searchText;
searchText = this.inputElement.val();
- result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
+ result =
+ searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
return this.renderList(result, searchText);
- // find file
+ // find file
}
// files pathes load
load(url) {
- axios.get(url)
+ axios
+ .get(url)
.then(({ data }) => {
this.element.find('.loading').hide();
this.filePaths = data;
this.findFile();
- this.element.find('.files-slider tr.tree-item').eq(0).addClass('selected').focus();
+ this.element
+ .find('.files-slider tr.tree-item')
+ .eq(0)
+ .addClass('selected')
+ .focus();
})
.catch(() => flash(__('An error occurred while loading filenames')));
}
@@ -89,7 +102,7 @@ export default class ProjectFindFile {
// render result
renderList(filePaths, searchText) {
var blobItemUrl, filePath, html, i, len, matches, results;
- this.element.find(".tree-table > tbody").empty();
+ this.element.find('.tree-table > tbody').empty();
results = [];
for (i = 0, len = filePaths.length; i < len; i += 1) {
@@ -100,9 +113,9 @@ export default class ProjectFindFile {
if (searchText) {
matches = fuzzaldrinPlus.match(filePath, searchText);
}
- blobItemUrl = this.options.blobUrlTemplate + "/" + filePath;
+ blobItemUrl = this.options.blobUrlTemplate + '/' + filePath;
html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
- results.push(this.element.find(".tree-table > tbody").append(html));
+ results.push(this.element.find('.tree-table > tbody').append(html));
}
return results;
}
@@ -110,52 +123,56 @@ export default class ProjectFindFile {
// make tbody row html
static makeHtml(filePath, matches, blobItemUrl) {
var $tr;
- $tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>");
+ $tr = $(
+ "<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>",
+ );
if (matches) {
- $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl));
+ $tr
+ .find('a')
+ .replaceWith(highlighter($tr.find('a'), filePath, matches).attr('href', blobItemUrl));
} else {
- $tr.find("a").attr("href", blobItemUrl);
- $tr.find(".str-truncated").text(filePath);
+ $tr.find('a').attr('href', blobItemUrl);
+ $tr.find('.str-truncated').text(filePath);
}
return $tr;
}
selectRow(type) {
var next, rows, selectedRow;
- rows = this.element.find(".files-slider tr.tree-item");
- selectedRow = this.element.find(".files-slider tr.tree-item.selected");
+ rows = this.element.find('.files-slider tr.tree-item');
+ selectedRow = this.element.find('.files-slider tr.tree-item.selected');
if (rows && rows.length > 0) {
if (selectedRow && selectedRow.length > 0) {
- if (type === "UP") {
+ if (type === 'UP') {
next = selectedRow.prev();
- } else if (type === "DOWN") {
+ } else if (type === 'DOWN') {
next = selectedRow.next();
}
if (next.length > 0) {
- selectedRow.removeClass("selected");
+ selectedRow.removeClass('selected');
selectedRow = next;
}
} else {
selectedRow = rows.eq(0);
}
- return selectedRow.addClass("selected").focus();
+ return selectedRow.addClass('selected').focus();
}
}
selectRowUp() {
- return this.selectRow("UP");
+ return this.selectRow('UP');
}
selectRowDown() {
- return this.selectRow("DOWN");
+ return this.selectRow('DOWN');
}
goToTree() {
- return window.location.href = this.options.treeUrl;
+ return (window.location.href = this.options.treeUrl);
}
goToBlob() {
- var $link = this.element.find(".tree-item.selected .tree-item-file-name a");
+ var $link = this.element.find('.tree-item.selected .tree-item-file-name a');
if ($link.length) {
$link.get(0).click();
diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js
index 5a0d2b642eb..a51a2a2242f 100644
--- a/app/assets/javascripts/project_import.js
+++ b/app/assets/javascripts/project_import.js
@@ -5,4 +5,3 @@ export default function projectImport() {
visitUrl(window.location.href);
}, 5000);
}
-
diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js
index 9049f87e037..d3c604dcee1 100644
--- a/app/assets/javascripts/project_label_subscription.js
+++ b/app/assets/javascripts/project_label_subscription.js
@@ -31,32 +31,35 @@ export default class ProjectLabelSubscription {
$btn.addClass('disabled');
- axios.post(url).then(() => {
- let newStatus;
- let newAction;
+ axios
+ .post(url)
+ .then(() => {
+ let newStatus;
+ let newAction;
- if (oldStatus === 'unsubscribed') {
- [newStatus, newAction] = ['subscribed', 'Unsubscribe'];
- } else {
- [newStatus, newAction] = ['unsubscribed', 'Subscribe'];
- }
+ if (oldStatus === 'unsubscribed') {
+ [newStatus, newAction] = ['subscribed', 'Unsubscribe'];
+ } else {
+ [newStatus, newAction] = ['unsubscribed', 'Subscribe'];
+ }
- $btn.removeClass('disabled');
+ $btn.removeClass('disabled');
- this.$buttons.attr('data-status', newStatus);
- this.$buttons.find('> span').text(newAction);
+ this.$buttons.attr('data-status', newStatus);
+ this.$buttons.find('> span').text(newAction);
- this.$buttons.map((i, button) => {
- const $button = $(button);
- const originalTitle = $button.attr('data-original-title');
+ this.$buttons.map((i, button) => {
+ const $button = $(button);
+ const originalTitle = $button.attr('data-original-title');
- if (originalTitle) {
- ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction);
- }
+ if (originalTitle) {
+ ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction);
+ }
- return button;
- });
- }).catch(() => flash(__('There was an error subscribing to this label.')));
+ return button;
+ });
+ })
+ .catch(() => flash(__('There was an error subscribing to this label.')));
}
static setNewTitle($button, originalTitle, newStatus) {
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index eaaeda8b339..f1fff173619 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -16,28 +16,28 @@ export default function projectSelect() {
this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled');
this.allowClear = $(select).data('allowClear') || false;
- placeholder = "Search for project";
+ placeholder = 'Search for project';
if (this.includeGroups) {
- placeholder += " or group";
+ placeholder += ' or group';
}
$(select).select2({
placeholder: placeholder,
minimumInputLength: 0,
- query: (function (_this) {
- return function (query) {
+ query: (function(_this) {
+ return function(query) {
var finalCallback, projectsCallback;
- finalCallback = function (projects) {
+ finalCallback = function(projects) {
var data;
data = {
- results: projects
+ results: projects,
};
return query.callback(data);
};
if (_this.includeGroups) {
- projectsCallback = function (projects) {
+ projectsCallback = function(projects) {
var groupsCallback;
- groupsCallback = function (groups) {
+ groupsCallback = function(groups) {
var data;
data = groups.concat(projects);
return finalCallback(data);
@@ -48,17 +48,26 @@ export default function projectSelect() {
projectsCallback = finalCallback;
}
if (_this.groupId) {
- return Api.groupProjects(_this.groupId, query.term, {
- with_issues_enabled: _this.withIssuesEnabled,
- with_merge_requests_enabled: _this.withMergeRequestsEnabled,
- }, projectsCallback);
+ return Api.groupProjects(
+ _this.groupId,
+ query.term,
+ {
+ with_issues_enabled: _this.withIssuesEnabled,
+ with_merge_requests_enabled: _this.withMergeRequestsEnabled,
+ },
+ projectsCallback,
+ );
} else {
- return Api.projects(query.term, {
- order_by: _this.orderBy,
- with_issues_enabled: _this.withIssuesEnabled,
- with_merge_requests_enabled: _this.withMergeRequestsEnabled,
- membership: !_this.allProjects,
- }, projectsCallback);
+ return Api.projects(
+ query.term,
+ {
+ order_by: _this.orderBy,
+ with_issues_enabled: _this.withIssuesEnabled,
+ with_merge_requests_enabled: _this.withMergeRequestsEnabled,
+ membership: !_this.allProjects,
+ },
+ projectsCallback,
+ );
}
};
})(this),
@@ -69,7 +78,7 @@ export default function projectSelect() {
url: project.web_url,
});
},
- text: function (project) {
+ text: function(project) {
return project.name_with_namespace || project.name;
},
@@ -79,7 +88,7 @@ export default function projectSelect() {
allowClear: this.allowClear,
- dropdownCssClass: "ajax-project-dropdown"
+ dropdownCssClass: 'ajax-project-dropdown',
});
if (simpleFilter) return select;
return new ProjectSelectComboButton(select);
diff --git a/app/assets/javascripts/project_select_combo_button.js b/app/assets/javascripts/project_select_combo_button.js
index 9b404896e86..3dbac3ff942 100644
--- a/app/assets/javascripts/project_select_combo_button.js
+++ b/app/assets/javascripts/project_select_combo_button.js
@@ -14,10 +14,11 @@ export default class ProjectSelectComboButton {
}
bindEvents() {
- this.projectSelectInput.siblings('.new-project-item-select-button')
+ this.projectSelectInput
+ .siblings('.new-project-item-select-button')
.on('click', e => this.openDropdown(e));
- this.newItemBtn.on('click', (e) => {
+ this.newItemBtn.on('click', e => {
if (!this.getProjectFromLocalStorage()) {
e.preventDefault();
this.openDropdown(e);
@@ -31,14 +32,21 @@ export default class ProjectSelectComboButton {
const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
if (localStorageIsSafe) {
- this.localStorageKey = ['group', this.groupId, this.formattedText.localStorageItemType, 'recent-project'].join('-');
+ this.localStorageKey = [
+ 'group',
+ this.groupId,
+ this.formattedText.localStorageItemType,
+ 'recent-project',
+ ].join('-');
this.setBtnTextFromLocalStorage();
}
}
// eslint-disable-next-line class-methods-use-this
openDropdown(event) {
- $(event.currentTarget).siblings('.project-item-select').select2('open');
+ $(event.currentTarget)
+ .siblings('.project-item-select')
+ .select2('open');
}
selectProject() {
@@ -86,8 +94,14 @@ export default class ProjectSelectComboButton {
const defaultTextPrefix = this.resourceLabel;
// the trailing slice call depluralizes each of these strings (e.g. new-issues -> new-issue)
- const localStorageItemType = `new-${this.resourceType.split('_').join('-').slice(0, -1)}`;
- const presetTextSuffix = this.resourceType.split('_').join(' ').slice(0, -1);
+ const localStorageItemType = `new-${this.resourceType
+ .split('_')
+ .join('-')
+ .slice(0, -1)}`;
+ const presetTextSuffix = this.resourceType
+ .split('_')
+ .join(' ')
+ .slice(0, -1);
return {
localStorageItemType, // new-issue / new-merge-request
@@ -96,4 +110,3 @@ export default class ProjectSelectComboButton {
};
}
}
-
diff --git a/app/assets/javascripts/project_visibility.js b/app/assets/javascripts/project_visibility.js
index a52ac768e57..aaf6723c85c 100644
--- a/app/assets/javascripts/project_visibility.js
+++ b/app/assets/javascripts/project_visibility.js
@@ -7,7 +7,7 @@ function setVisibilityOptions(namespaceSelector) {
const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex];
const { name, visibility, visibilityLevel, showPath, editPath } = selectedNamespace.dataset;
- document.querySelectorAll('.visibility-level-setting .form-check').forEach((option) => {
+ document.querySelectorAll('.visibility-level-setting .form-check').forEach(option => {
const optionInput = option.querySelector('input[type=radio]');
const optionValue = optionInput ? optionInput.value : 0;
const optionTitle = option.querySelector('.option-title');
@@ -20,8 +20,7 @@ function setVisibilityOptions(namespaceSelector) {
optionInput.disabled = true;
const reason = option.querySelector('.option-disabled-reason');
if (reason) {
- reason.innerHTML =
- `This project cannot be ${optionName} because the visibility of
+ reason.innerHTML = `This project cannot be ${optionName} because the visibility of
<a href="${showPath}">${name}</a> is ${visibility}. To make this project
${optionName}, you must first <a href="${editPath}">change the visibility</a>
of the parent group.`;
diff --git a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
index 078ccbbbac2..8380cfb6c59 100644
--- a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
+++ b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js
@@ -65,10 +65,14 @@ export default class PrometheusMetrics {
let totalMissingEnvVarMetrics = 0;
let totalExporters = 0;
- metrics.forEach((metric) => {
+ metrics.forEach(metric => {
if (metric.active_metrics > 0) {
totalExporters += 1;
- this.$monitoredMetricsList.append(`<li>${_.escape(metric.group)}<span class="badge">${_.escape(metric.active_metrics)}</span></li>`);
+ this.$monitoredMetricsList.append(
+ `<li>${_.escape(metric.group)}<span class="badge">${_.escape(
+ metric.active_metrics,
+ )}</span></li>`,
+ );
totalMonitoredMetrics += metric.active_metrics;
if (metric.metrics_missing_requirements > 0) {
this.$missingEnvVarMetricsList.append(`<li>${_.escape(metric.group)}</li>`);
@@ -78,17 +82,26 @@ export default class PrometheusMetrics {
});
if (totalMonitoredMetrics === 0) {
- const emptyCommonMetricsText = sprintf(s__('PrometheusService|<p class="text-tertiary">No <a href="%{docsUrl}">common metrics</a> were found</p>'), {
- docsUrl: this.helpMetricsPath,
- }, false);
+ const emptyCommonMetricsText = sprintf(
+ s__(
+ 'PrometheusService|<p class="text-tertiary">No <a href="%{docsUrl}">common metrics</a> were found</p>',
+ ),
+ {
+ docsUrl: this.helpMetricsPath,
+ },
+ false,
+ );
this.$monitoredMetricsEmpty.empty();
this.$monitoredMetricsEmpty.append(emptyCommonMetricsText);
this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
} else {
- const metricsCountText = sprintf(s__('PrometheusService|%{exporters} with %{metrics} were found'), {
- exporters: n__('%d exporter', '%d exporters', totalExporters),
- metrics: n__('%d metric', '%d metrics', totalMonitoredMetrics),
- });
+ const metricsCountText = sprintf(
+ s__('PrometheusService|%{exporters} with %{metrics} were found'),
+ {
+ exporters: n__('%d exporter', '%d exporters', totalExporters),
+ metrics: n__('%d metric', '%d metrics', totalMonitoredMetrics),
+ },
+ );
this.$monitoredMetricsCount.text(metricsCountText);
this.showMonitoringMetricsPanelState(PANEL_STATE.LIST);
@@ -102,7 +115,8 @@ export default class PrometheusMetrics {
loadActiveMetrics() {
this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
backOff((next, stop) => {
- axios.get(this.activeMetricsEndpoint)
+ axios
+ .get(this.activeMetricsEndpoint)
.then(({ data }) => {
if (data && data.success) {
stop(data);
@@ -117,7 +131,7 @@ export default class PrometheusMetrics {
})
.catch(stop);
})
- .then((res) => {
+ .then(res => {
if (res && res.data && res.data.length) {
this.populateActiveMetrics(res.data);
} else {
diff --git a/app/assets/javascripts/raven/raven_config.js b/app/assets/javascripts/raven/raven_config.js
index 658caeecde1..338006ce2b9 100644
--- a/app/assets/javascripts/raven/raven_config.js
+++ b/app/assets/javascripts/raven/raven_config.js
@@ -9,7 +9,7 @@ const IGNORE_ERRORS = [
'canvas.contentDocument',
'MyApp_RemoveAllHighlights',
'http://tt.epicplay.com',
- 'Can\'t find variable: ZiteReader',
+ "Can't find variable: ZiteReader",
'jigsaw is not defined',
'ComboSearch is not defined',
'http://loading.retry.widdit.com/',
diff --git a/app/assets/javascripts/ref_select_dropdown.js b/app/assets/javascripts/ref_select_dropdown.js
index 95c5cf7b345..75bac035aca 100644
--- a/app/assets/javascripts/ref_select_dropdown.js
+++ b/app/assets/javascripts/ref_select_dropdown.js
@@ -2,7 +2,8 @@ import $ from 'jquery';
class RefSelectDropdown {
constructor($dropdownButton, availableRefs) {
- const availableRefsValue = availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML);
+ const availableRefsValue =
+ availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML);
$dropdownButton.glDropdown({
data: availableRefsValue,
filterable: true,
@@ -29,7 +30,7 @@ class RefSelectDropdown {
const $fieldInput = $(`input[name="${$dropdownButton.data('fieldName')}"]`, $dropdownContainer);
const $filterInput = $('input[type="search"]', $dropdownContainer);
- $filterInput.on('keyup', (e) => {
+ $filterInput.on('keyup', e => {
const keyCode = e.keyCode || e.which;
if (keyCode !== 13) return;
diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
index 9f3a41180cc..bd204503cc7 100644
--- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
@@ -75,7 +75,7 @@ export default {
:loading-text="groupedSummaryText"
:error-text="groupedSummaryText"
:has-issues="reports.length > 0"
- class="mr-widget-border-top grouped-security-reports mr-report"
+ class="mr-widget-section grouped-security-reports mr-report"
>
<div
slot="body"
diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js
index b38421bdf1c..d22aca35e09 100644
--- a/app/assets/javascripts/settings_panels.js
+++ b/app/assets/javascripts/settings_panels.js
@@ -3,10 +3,14 @@ import { __ } from './locale';
function expandSection($section) {
$section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Collapse'));
- $section.find('.settings-content').off('scroll.expandSection').scrollTop(0);
+ $section
+ .find('.settings-content')
+ .off('scroll.expandSection')
+ .scrollTop(0);
$section.addClass('expanded');
if (!$section.hasClass('no-animate')) {
- $section.addClass('animating')
+ $section
+ .addClass('animating')
.one('animationend.animateSection', () => $section.removeClass('animating'));
}
}
@@ -16,7 +20,8 @@ function closeSection($section) {
$section.find('.settings-content').on('scroll.expandSection', () => expandSection($section));
$section.removeClass('expanded');
if (!$section.hasClass('no-animate')) {
- $section.addClass('animating')
+ $section
+ .addClass('animating')
.one('animationend.animateSection', () => $section.removeClass('animating'));
}
}
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
index a7bffa81045..259858e4b46 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
@@ -1,6 +1,6 @@
<script>
import { __, sprintf } from '~/locale';
-import { abbreviateTime } from '~/lib/utils/pretty_time';
+import { abbreviateTime } from '~/lib/utils/datetime_utility';
import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
index dc599e1b9fc..e74912d628f 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue
@@ -1,5 +1,5 @@
<script>
-import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time';
+import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import tooltip from '../../../vue_shared/directives/tooltip';
export default {
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index 99c93952e2a..f2b9d75dd00 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -10,8 +10,10 @@ import syntaxHighlight from './syntax_highlight';
const WRAPPER = '<div class="diff-content"></div>';
const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
-const ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
-const COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <button class="click-to-expand btn btn-link">Click to expand it.</button></div>';
+const ERROR_HTML =
+ '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
+const COLLAPSED_HTML =
+ '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <button class="click-to-expand btn btn-link">Click to expand it.</button></div>';
export default class SingleFileDiff {
constructor(file) {
@@ -23,23 +25,36 @@ export default class SingleFileDiff {
this.isOpen = !this.diffForPath;
if (this.diffForPath) {
this.collapsedContent = this.content;
- this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide();
+ this.loadingContent = $(WRAPPER)
+ .addClass('loading')
+ .html(LOADING_HTML)
+ .hide();
this.content = null;
this.collapsedContent.after(this.loadingContent);
this.$toggleIcon.addClass('fa-caret-right');
} else {
- this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide();
+ this.collapsedContent = $(WRAPPER)
+ .html(COLLAPSED_HTML)
+ .hide();
this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down');
}
- $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) {
- this.toggleDiff($(e.target));
- }).bind(this));
+ $('.js-file-title, .click-to-expand', this.file).on(
+ 'click',
+ function(e) {
+ this.toggleDiff($(e.target));
+ }.bind(this),
+ );
}
toggleDiff($target, cb) {
- if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
+ if (
+ !$target.hasClass('js-file-title') &&
+ !$target.hasClass('click-to-expand') &&
+ !$target.hasClass('diff-toggle-caret')
+ )
+ return;
this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) {
this.content.hide();
@@ -65,7 +80,8 @@ export default class SingleFileDiff {
this.collapsedContent.hide();
this.loadingContent.show();
- axios.get(this.diffForPath)
+ axios
+ .get(this.diffForPath)
.then(({ data }) => {
this.loadingContent.hide();
if (data.html) {
diff --git a/app/assets/javascripts/smart_interval.js b/app/assets/javascripts/smart_interval.js
index 5e385400747..8ca590123ae 100644
--- a/app/assets/javascripts/smart_interval.js
+++ b/app/assets/javascripts/smart_interval.js
@@ -93,7 +93,9 @@ export default class SmartInterval {
destroy() {
this.cancel();
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
- $(document).off('visibilitychange').off('beforeunload');
+ $(document)
+ .off('visibilitychange')
+ .off('beforeunload');
}
/* private */
@@ -111,11 +113,12 @@ export default class SmartInterval {
triggerCallback() {
this.isLoading = true;
- this.cfg.callback()
+ this.cfg
+ .callback()
.then(() => {
this.isLoading = false;
})
- .catch((err) => {
+ .catch(err => {
this.isLoading = false;
throw err;
});
@@ -134,9 +137,9 @@ export default class SmartInterval {
handleVisibilityChange(e) {
this.state.pageVisibility = e.target.visibilityState;
- const intervalAction = this.isPageVisible() ?
- this.onVisibilityVisible :
- this.onVisibilityHidden;
+ const intervalAction = this.isPageVisible()
+ ? this.onVisibilityVisible
+ : this.onVisibilityHidden;
intervalAction.apply(this);
}
@@ -162,7 +165,9 @@ export default class SmartInterval {
this.setCurrentInterval(nextInterval);
}
- isPageVisible() { return this.state.pageVisibility === 'visible'; }
+ isPageVisible() {
+ return this.state.pageVisibility === 'visible';
+ }
stopTimer() {
const { state } = this;
@@ -170,4 +175,3 @@ export default class SmartInterval {
state.intervalId = window.clearInterval(state.intervalId);
}
}
-
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index f5a7fdae5d7..007b83e1927 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -11,10 +11,14 @@ export default class Star {
const $starSpan = $this.find('span');
const $startIcon = $this.find('svg');
- axios.post($this.data('endpoint'))
+ axios
+ .post($this.data('endpoint'))
.then(({ data }) => {
const isStarred = $starSpan.hasClass('starred');
- $this.parent().find('.star-count').text(data.star_count);
+ $this
+ .parent()
+ .find('.star-count')
+ .text(data.star_count);
if (isStarred) {
$starSpan.removeClass('starred').text(s__('StarProject|Star'));
diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js
index 48782e63b9b..edefb3735d7 100644
--- a/app/assets/javascripts/task_list.js
+++ b/app/assets/javascripts/task_list.js
@@ -26,7 +26,11 @@ export default class TaskList {
// Prevent duplicate event bindings
this.disable();
$(`${this.selector} .js-task-list-container`).taskList('enable');
- $(document).on('tasklist:changed', `${this.selector} .js-task-list-container`, this.update.bind(this));
+ $(document).on(
+ 'tasklist:changed',
+ `${this.selector} .js-task-list-container`,
+ this.update.bind(this),
+ );
}
disable() {
@@ -41,7 +45,8 @@ export default class TaskList {
[this.fieldName]: $target.val(),
};
- return axios.patch($target.data('updateUrl') || $('form.js-issuable-update').attr('action'), patchData)
+ return axios
+ .patch($target.data('updateUrl') || $('form.js-issuable-update').attr('action'), patchData)
.then(({ data }) => this.onSuccess(data))
.catch(err => this.onError(err));
}
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js
index 74166313940..6065770e68d 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
@@ -31,12 +31,18 @@ export default class IssuableTemplateSelector extends TemplateSelector {
requestFile(query) {
this.startLoadingSpinner();
- Api.issueTemplate(this.namespacePath, this.projectPath, query.name, this.issuableType, (err, currentTemplate) => {
- this.currentTemplate = currentTemplate;
- this.stopLoadingSpinner();
- if (err) return; // Error handled by global AJAX error handler
- this.setInputValueToTemplateContent();
- });
+ Api.issueTemplate(
+ this.namespacePath,
+ this.projectPath,
+ query.name,
+ this.issuableType,
+ (err, currentTemplate) => {
+ this.currentTemplate = currentTemplate;
+ this.stopLoadingSpinner();
+ if (err) return; // Error handled by global AJAX error handler
+ this.setInputValueToTemplateContent();
+ },
+ );
return;
}
diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js
index 74c5bbe45a4..b24aa8a3a34 100644
--- a/app/assets/javascripts/terminal/terminal.js
+++ b/app/assets/javascripts/terminal/terminal.js
@@ -4,10 +4,14 @@ import * as fit from 'xterm/lib/addons/fit/fit';
export default class GLTerminal {
constructor(options = {}) {
- this.options = Object.assign({}, {
- cursorBlink: true,
- screenKeys: true,
- }, options);
+ this.options = Object.assign(
+ {},
+ {
+ cursorBlink: true,
+ screenKeys: true,
+ },
+ options,
+ );
this.container = document.querySelector(options.selector);
diff --git a/app/assets/javascripts/test_utils/simulate_drag.js b/app/assets/javascripts/test_utils/simulate_drag.js
index a5c18042ce7..be9ebc81c6b 100644
--- a/app/assets/javascripts/test_utils/simulate_drag.js
+++ b/app/assets/javascripts/test_utils/simulate_drag.js
@@ -4,15 +4,43 @@ function simulateEvent(el, type, options = {}) {
if (/^mouse/.test(type)) {
event = el.ownerDocument.createEvent('MouseEvents');
- event.initMouseEvent(type, true, true, el.ownerDocument.defaultView,
- options.button, options.screenX, options.screenY, options.clientX, options.clientY,
- options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el);
+ event.initMouseEvent(
+ type,
+ true,
+ true,
+ el.ownerDocument.defaultView,
+ options.button,
+ options.screenX,
+ options.screenY,
+ options.clientX,
+ options.clientY,
+ options.ctrlKey,
+ options.altKey,
+ options.shiftKey,
+ options.metaKey,
+ options.button,
+ el,
+ );
} else {
event = el.ownerDocument.createEvent('CustomEvent');
- event.initCustomEvent(type, true, true, el.ownerDocument.defaultView,
- options.button, options.screenX, options.screenY, options.clientX, options.clientY,
- options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el);
+ event.initCustomEvent(
+ type,
+ true,
+ true,
+ el.ownerDocument.defaultView,
+ options.button,
+ options.screenX,
+ options.screenY,
+ options.clientX,
+ options.clientY,
+ options.ctrlKey,
+ options.altKey,
+ options.shiftKey,
+ options.metaKey,
+ options.button,
+ el,
+ );
event.dataTransfer = {
data: {},
@@ -37,14 +65,16 @@ function simulateEvent(el, type, options = {}) {
}
function isLast(target) {
- const el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
+ const el =
+ typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
const { children } = el;
return children.length - 1 === target.index;
}
function getTarget(target) {
- const el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
+ const el =
+ typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
const { children } = el;
return (
@@ -58,13 +88,13 @@ function getTarget(target) {
function getRect(el) {
const rect = el.getBoundingClientRect();
const width = rect.right - rect.left;
- const height = (rect.bottom - rect.top) + 10;
+ const height = rect.bottom - rect.top + 10;
return {
x: rect.left,
y: rect.top,
- cx: rect.left + (width / 2),
- cy: rect.top + (height / 2),
+ cx: rect.left + width / 2,
+ cy: rect.top + height / 2,
w: width,
h: height,
hw: width / 2,
@@ -112,8 +142,8 @@ export default function simulateDrag(options) {
const dragInterval = setInterval(() => {
const progress = (new Date().getTime() - startTime) / duration;
- const x = (fromRect.cx + ((toRect.cx - fromRect.cx) * progress));
- const y = (fromRect.cy + ((toRect.cy - fromRect.cy) * progress));
+ const x = fromRect.cx + (toRect.cx - fromRect.cx) * progress;
+ const y = fromRect.cy + (toRect.cy - fromRect.cy) * progress;
const overEl = fromEl.ownerDocument.elementFromPoint(x, y);
simulateEvent(overEl, 'mousemove', {
diff --git a/app/assets/javascripts/test_utils/simulate_input.js b/app/assets/javascripts/test_utils/simulate_input.js
index 90c1b7cb57e..c300c806e6d 100644
--- a/app/assets/javascripts/test_utils/simulate_input.js
+++ b/app/assets/javascripts/test_utils/simulate_input.js
@@ -12,7 +12,7 @@ export default function simulateInput(target, text) {
}
if (text.length > 0) {
- Array.prototype.forEach.call(text, (char) => {
+ Array.prototype.forEach.call(text, char => {
input.value += char;
triggerEvents(input);
});
diff --git a/app/assets/javascripts/toggle_buttons.js b/app/assets/javascripts/toggle_buttons.js
index 199b14458ed..d83ffc7e211 100644
--- a/app/assets/javascripts/toggle_buttons.js
+++ b/app/assets/javascripts/toggle_buttons.js
@@ -49,7 +49,7 @@ function onToggleClicked(toggle, input, clickCallback) {
export default function setupToggleButtons(container, clickCallback = () => {}) {
const toggles = container.querySelectorAll('.js-project-feature-toggle');
- toggles.forEach((toggle) => {
+ toggles.forEach(toggle => {
const input = toggle.querySelector('.js-project-feature-toggle-input');
const isOn = convertPermissionToBoolean(input.value);
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index 066fd6278a7..3e659c9e7ea 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -8,7 +8,7 @@ export default class TreeView {
this.initKeyNav();
// Code browser tree slider
// Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
- $(".tree-content-holder .tree-item").on('click', function(e) {
+ $('.tree-content-holder .tree-item').on('click', function(e) {
var $clickedEl, path;
$clickedEl = $(e.target);
path = $('.tree-item-file-name a', this).attr('href');
@@ -27,33 +27,33 @@ export default class TreeView {
initKeyNav() {
var li, liSelected;
- li = $("tr.tree-item");
+ li = $('tr.tree-item');
liSelected = null;
return $('body').keydown(function(e) {
var next, path;
- if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) {
+ if ($('input:focus').length > 0 && (e.which === 38 || e.which === 40)) {
return false;
}
if (e.which === 40) {
if (liSelected) {
next = liSelected.next();
if (next.length > 0) {
- liSelected.removeClass("selected");
- liSelected = next.addClass("selected");
+ liSelected.removeClass('selected');
+ liSelected = next.addClass('selected');
}
} else {
- liSelected = li.eq(0).addClass("selected");
+ liSelected = li.eq(0).addClass('selected');
}
return $(liSelected).focus();
} else if (e.which === 38) {
if (liSelected) {
next = liSelected.prev();
if (next.length > 0) {
- liSelected.removeClass("selected");
- liSelected = next.addClass("selected");
+ liSelected.removeClass('selected');
+ liSelected = next.addClass('selected');
}
} else {
- liSelected = li.last().addClass("selected");
+ liSelected = li.last().addClass('selected');
}
return $(liSelected).focus();
} else if (e.which === 13) {
diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js
index 78fd7ad441f..abfc81e681e 100644
--- a/app/assets/javascripts/u2f/authenticate.js
+++ b/app/assets/javascripts/u2f/authenticate.js
@@ -49,7 +49,7 @@ export default class U2FAuthenticate {
start() {
return importU2FLibrary()
- .then((utils) => {
+ .then(utils => {
this.u2fUtils = utils;
this.renderInProgress();
})
@@ -57,14 +57,19 @@ export default class U2FAuthenticate {
}
authenticate() {
- return this.u2fUtils.sign(this.appId, this.challenge, this.signRequests,
- (response) => {
+ return this.u2fUtils.sign(
+ this.appId,
+ this.challenge,
+ this.signRequests,
+ response => {
if (response.errorCode) {
const error = new U2FError(response.errorCode, 'authenticate');
return this.renderError(error);
}
return this.renderAuthenticated(JSON.stringify(response));
- }, 10);
+ },
+ 10,
+ );
}
renderTemplate(name, params) {
@@ -99,5 +104,4 @@ export default class U2FAuthenticate {
this.container[0].classList.add('hidden');
this.fallbackUI.classList.remove('hidden');
}
-
}
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
index 01e259a741d..43c814c8070 100644
--- a/app/assets/javascripts/u2f/register.js
+++ b/app/assets/javascripts/u2f/register.js
@@ -34,7 +34,7 @@ export default class U2FRegister {
start() {
return importU2FLibrary()
- .then((utils) => {
+ .then(utils => {
this.u2fUtils = utils;
this.renderSetup();
})
@@ -42,14 +42,19 @@ export default class U2FRegister {
}
register() {
- return this.u2fUtils.register(this.appId, this.registerRequests, this.signRequests,
- (response) => {
+ return this.u2fUtils.register(
+ this.appId,
+ this.registerRequests,
+ this.signRequests,
+ response => {
if (response.errorCode) {
const error = new U2FError(response.errorCode, 'register');
return this.renderError(error);
}
return this.renderRegistered(JSON.stringify(response));
- }, 10);
+ },
+ 10,
+ );
}
renderTemplate(name, params) {
diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js
index 5778f00332d..b706481c02f 100644
--- a/app/assets/javascripts/u2f/util.js
+++ b/app/assets/javascripts/u2f/util.js
@@ -19,11 +19,10 @@ function getChromeVersion(userAgent) {
export function canInjectU2fApi(userAgent) {
const isSupportedChrome = isChrome(userAgent) && getChromeVersion(userAgent) >= 41;
const isSupportedOpera = isOpera(userAgent) && getOperaVersion(userAgent) >= 40;
- const isMobile = (
+ const isMobile =
userAgent.indexOf('droid') >= 0 ||
userAgent.indexOf('CriOS') >= 0 ||
- /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent)
- );
+ /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
return (isSupportedChrome || isSupportedOpera) && !isMobile;
}
diff --git a/app/assets/javascripts/ui_development_kit.js b/app/assets/javascripts/ui_development_kit.js
index 9b242ea779d..be18ac5da24 100644
--- a/app/assets/javascripts/ui_development_kit.js
+++ b/app/assets/javascripts/ui_development_kit.js
@@ -4,13 +4,17 @@ import Api from './api';
export default () => {
$('#js-project-dropdown').glDropdown({
data: (term, callback) => {
- Api.projects(term, {
- order_by: 'last_activity_at',
- }, (data) => {
- callback(data);
- });
+ Api.projects(
+ term,
+ {
+ order_by: 'last_activity_at',
+ },
+ data => {
+ callback(data);
+ },
+ );
},
- text: project => (project.name_with_namespace || project.name),
+ text: project => project.name_with_namespace || project.name,
selectable: true,
fieldName: 'author_id',
filterable: true,
@@ -18,6 +22,6 @@ export default () => {
fields: ['name_with_namespace'],
},
id: data => data.id,
- isSelected: data => (data.id === 2),
+ isSelected: data => data.id === 2,
});
};
diff --git a/app/assets/javascripts/usage_ping_consent.js b/app/assets/javascripts/usage_ping_consent.js
index ae3fde190e3..05607f09a7e 100644
--- a/app/assets/javascripts/usage_ping_consent.js
+++ b/app/assets/javascripts/usage_ping_consent.js
@@ -4,7 +4,7 @@ import Flash, { hideFlash } from './flash';
import { convertPermissionToBoolean } from './lib/utils/common_utils';
export default () => {
- $('body').on('click', '.js-usage-consent-action', (e) => {
+ $('body').on('click', '.js-usage-consent-action', e => {
e.preventDefault();
e.stopImmediatePropagation(); // overwrite rails listener
@@ -18,7 +18,8 @@ export default () => {
const hideConsentMessage = () => hideFlash(document.querySelector('.ping-consent-message'));
- axios.put(url, data)
+ axios
+ .put(url, data)
.then(() => {
hideConsentMessage();
})
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index e2259a8d07b..4b090212d83 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -15,8 +15,8 @@ function UsersSelect(currentUser, els, options = {}) {
var $els;
this.users = this.users.bind(this);
this.user = this.user.bind(this);
- this.usersPath = "/autocomplete/users.json";
- this.userPath = "/autocomplete/users/:id.json";
+ this.usersPath = '/autocomplete/users.json';
+ this.userPath = '/autocomplete/users/:id.json';
if (currentUser != null) {
if (typeof currentUser === 'object') {
this.currentUser = currentUser;
@@ -33,156 +33,180 @@ function UsersSelect(currentUser, els, options = {}) {
$els = $('.js-user-search');
}
- $els.each((function(_this) {
- return function(i, dropdown) {
- var options = {};
- var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, defaultNullUser, firstUser, issueURL, selectedId, selectedIdDefault, showAnyUser, showNullUser, showMenuAbove;
- $dropdown = $(dropdown);
- options.projectId = $dropdown.data('projectId');
- options.groupId = $dropdown.data('groupId');
- options.showCurrentUser = $dropdown.data('currentUser');
- options.todoFilter = $dropdown.data('todoFilter');
- options.todoStateFilter = $dropdown.data('todoStateFilter');
- showNullUser = $dropdown.data('nullUser');
- defaultNullUser = $dropdown.data('nullUserDefault');
- showMenuAbove = $dropdown.data('showMenuAbove');
- showAnyUser = $dropdown.data('anyUser');
- firstUser = $dropdown.data('firstUser');
- options.authorId = $dropdown.data('authorId');
- defaultLabel = $dropdown.data('defaultLabel');
- issueURL = $dropdown.data('issueUpdate');
- $selectbox = $dropdown.closest('.selectbox');
- $block = $selectbox.closest('.block');
- abilityName = $dropdown.data('abilityName');
- $value = $block.find('.value');
- $collapsedSidebar = $block.find('.sidebar-collapsed-user');
- $loading = $block.find('.block-loading').fadeOut();
- selectedIdDefault = (defaultNullUser && showNullUser) ? 0 : null;
- selectedId = $dropdown.data('selected');
-
- if (selectedId === undefined) {
- selectedId = selectedIdDefault;
- }
-
- const assignYourself = function () {
- const unassignedSelected = $dropdown.closest('.selectbox')
- .find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
-
- if (unassignedSelected) {
- unassignedSelected.remove();
+ $els.each(
+ (function(_this) {
+ return function(i, dropdown) {
+ var options = {};
+ var $block,
+ $collapsedSidebar,
+ $dropdown,
+ $loading,
+ $selectbox,
+ $value,
+ abilityName,
+ assignTo,
+ assigneeTemplate,
+ collapsedAssigneeTemplate,
+ defaultLabel,
+ defaultNullUser,
+ firstUser,
+ issueURL,
+ selectedId,
+ selectedIdDefault,
+ showAnyUser,
+ showNullUser,
+ showMenuAbove;
+ $dropdown = $(dropdown);
+ options.projectId = $dropdown.data('projectId');
+ options.groupId = $dropdown.data('groupId');
+ options.showCurrentUser = $dropdown.data('currentUser');
+ options.todoFilter = $dropdown.data('todoFilter');
+ options.todoStateFilter = $dropdown.data('todoStateFilter');
+ showNullUser = $dropdown.data('nullUser');
+ defaultNullUser = $dropdown.data('nullUserDefault');
+ showMenuAbove = $dropdown.data('showMenuAbove');
+ showAnyUser = $dropdown.data('anyUser');
+ firstUser = $dropdown.data('firstUser');
+ options.authorId = $dropdown.data('authorId');
+ defaultLabel = $dropdown.data('defaultLabel');
+ issueURL = $dropdown.data('issueUpdate');
+ $selectbox = $dropdown.closest('.selectbox');
+ $block = $selectbox.closest('.block');
+ abilityName = $dropdown.data('abilityName');
+ $value = $block.find('.value');
+ $collapsedSidebar = $block.find('.sidebar-collapsed-user');
+ $loading = $block.find('.block-loading').fadeOut();
+ selectedIdDefault = defaultNullUser && showNullUser ? 0 : null;
+ selectedId = $dropdown.data('selected');
+
+ if (selectedId === undefined) {
+ selectedId = selectedIdDefault;
}
- // Save current selected user to the DOM
- const input = document.createElement('input');
- input.type = 'hidden';
- input.name = $dropdown.data('fieldName');
+ const assignYourself = function() {
+ const unassignedSelected = $dropdown
+ .closest('.selectbox')
+ .find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
- const currentUserInfo = $dropdown.data('currentUserInfo');
-
- if (currentUserInfo) {
- input.value = currentUserInfo.id;
- input.dataset.meta = _.escape(currentUserInfo.name);
- } else if (_this.currentUser) {
- input.value = _this.currentUser.id;
- }
-
- if ($selectbox) {
- $dropdown.parent().before(input);
- } else {
- $dropdown.after(input);
- }
- };
-
- if ($block[0]) {
- $block[0].addEventListener('assignYourself', assignYourself);
- }
-
- const getSelectedUserInputs = function() {
- return $selectbox
- .find(`input[name="${$dropdown.data('fieldName')}"]`);
- };
+ if (unassignedSelected) {
+ unassignedSelected.remove();
+ }
- const getSelected = function() {
- return getSelectedUserInputs()
- .map((index, input) => parseInt(input.value, 10))
- .get();
- };
+ // Save current selected user to the DOM
+ const input = document.createElement('input');
+ input.type = 'hidden';
+ input.name = $dropdown.data('fieldName');
- const checkMaxSelect = function() {
- const maxSelect = $dropdown.data('maxSelect');
- if (maxSelect) {
- const selected = getSelected();
+ const currentUserInfo = $dropdown.data('currentUserInfo');
- if (selected.length > maxSelect) {
- const firstSelectedId = selected[0];
- const firstSelected = $dropdown.closest('.selectbox')
- .find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`);
+ if (currentUserInfo) {
+ input.value = currentUserInfo.id;
+ input.dataset.meta = _.escape(currentUserInfo.name);
+ } else if (_this.currentUser) {
+ input.value = _this.currentUser.id;
+ }
- firstSelected.remove();
- emitSidebarEvent('sidebar.removeAssignee', {
- id: firstSelectedId,
- });
+ if ($selectbox) {
+ $dropdown.parent().before(input);
+ } else {
+ $dropdown.after(input);
}
- }
- };
+ };
- const getMultiSelectDropdownTitle = function(selectedUser, isSelected) {
- const selectedUsers = getSelected()
- .filter(u => u !== 0);
-
- const firstUser = getSelectedUserInputs()
- .map((index, input) => ({
- name: input.dataset.meta,
- value: parseInt(input.value, 10),
- }))
- .filter(u => u.id !== 0)
- .get(0);
-
- if (selectedUsers.length === 0) {
- return 'Unassigned';
- } else if (selectedUsers.length === 1) {
- return firstUser.name;
- } else if (isSelected) {
- const otherSelected = selectedUsers.filter(s => s !== selectedUser.id);
- return `${selectedUser.name} + ${otherSelected.length} more`;
- } else {
- return `${firstUser.name} + ${selectedUsers.length - 1} more`;
+ if ($block[0]) {
+ $block[0].addEventListener('assignYourself', assignYourself);
}
- };
- $('.assign-to-me-link').on('click', (e) => {
- e.preventDefault();
- $(e.currentTarget).hide();
+ const getSelectedUserInputs = function() {
+ return $selectbox.find(`input[name="${$dropdown.data('fieldName')}"]`);
+ };
+
+ const getSelected = function() {
+ return getSelectedUserInputs()
+ .map((index, input) => parseInt(input.value, 10))
+ .get();
+ };
+
+ const checkMaxSelect = function() {
+ const maxSelect = $dropdown.data('maxSelect');
+ if (maxSelect) {
+ const selected = getSelected();
+
+ if (selected.length > maxSelect) {
+ const firstSelectedId = selected[0];
+ const firstSelected = $dropdown
+ .closest('.selectbox')
+ .find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`);
+
+ firstSelected.remove();
+ emitSidebarEvent('sidebar.removeAssignee', {
+ id: firstSelectedId,
+ });
+ }
+ }
+ };
+
+ const getMultiSelectDropdownTitle = function(selectedUser, isSelected) {
+ const selectedUsers = getSelected().filter(u => u !== 0);
+
+ const firstUser = getSelectedUserInputs()
+ .map((index, input) => ({
+ name: input.dataset.meta,
+ value: parseInt(input.value, 10),
+ }))
+ .filter(u => u.id !== 0)
+ .get(0);
+
+ if (selectedUsers.length === 0) {
+ return 'Unassigned';
+ } else if (selectedUsers.length === 1) {
+ return firstUser.name;
+ } else if (isSelected) {
+ const otherSelected = selectedUsers.filter(s => s !== selectedUser.id);
+ return `${selectedUser.name} + ${otherSelected.length} more`;
+ } else {
+ return `${firstUser.name} + ${selectedUsers.length - 1} more`;
+ }
+ };
- if ($dropdown.data('multiSelect')) {
- assignYourself();
- checkMaxSelect();
+ $('.assign-to-me-link').on('click', e => {
+ e.preventDefault();
+ $(e.currentTarget).hide();
- const currentUserInfo = $dropdown.data('currentUserInfo');
- $dropdown.find('.dropdown-toggle-text').text(getMultiSelectDropdownTitle(currentUserInfo)).removeClass('is-default');
- } else {
- const $input = $(`input[name="${$dropdown.data('fieldName')}"]`);
- $input.val(gon.current_user_id);
- selectedId = $input.val();
- $dropdown.find('.dropdown-toggle-text').text(gon.current_user_fullname).removeClass('is-default');
- }
- });
-
- $block.on('click', '.js-assign-yourself', (e) => {
- e.preventDefault();
- return assignTo(_this.currentUser.id);
- });
-
- assignTo = function(selected) {
- var data;
- data = {};
- data[abilityName] = {};
- data[abilityName].assignee_id = selected != null ? selected : null;
- $loading.removeClass('hidden').fadeIn();
- $dropdown.trigger('loading.gl.dropdown');
-
- return axios.put(issueURL, data)
- .then(({ data }) => {
+ if ($dropdown.data('multiSelect')) {
+ assignYourself();
+ checkMaxSelect();
+
+ const currentUserInfo = $dropdown.data('currentUserInfo');
+ $dropdown
+ .find('.dropdown-toggle-text')
+ .text(getMultiSelectDropdownTitle(currentUserInfo))
+ .removeClass('is-default');
+ } else {
+ const $input = $(`input[name="${$dropdown.data('fieldName')}"]`);
+ $input.val(gon.current_user_id);
+ selectedId = $input.val();
+ $dropdown
+ .find('.dropdown-toggle-text')
+ .text(gon.current_user_fullname)
+ .removeClass('is-default');
+ }
+ });
+
+ $block.on('click', '.js-assign-yourself', e => {
+ e.preventDefault();
+ return assignTo(_this.currentUser.id);
+ });
+
+ assignTo = function(selected) {
+ var data;
+ data = {};
+ data[abilityName] = {};
+ data[abilityName].assignee_id = selected != null ? selected : null;
+ $loading.removeClass('hidden').fadeIn();
+ $dropdown.trigger('loading.gl.dropdown');
+
+ return axios.put(issueURL, data).then(({ data }) => {
var user, tooltipTitle;
$dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut();
@@ -190,14 +214,14 @@ function UsersSelect(currentUser, els, options = {}) {
user = {
name: data.assignee.name,
username: data.assignee.username,
- avatar: data.assignee.avatar_url
+ avatar: data.assignee.avatar_url,
};
tooltipTitle = _.escape(user.name);
} else {
user = {
name: 'Unassigned',
username: '',
- avatar: ''
+ avatar: '',
};
tooltipTitle = __('Assignee');
}
@@ -205,319 +229,341 @@ function UsersSelect(currentUser, els, options = {}) {
$collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle');
return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
});
- };
- collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>');
- assigneeTemplate = _.template('<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>');
- return $dropdown.glDropdown({
- showMenuAbove: showMenuAbove,
- data: function(term, callback) {
- return _this.users(term, options, function(users) {
- // GitLabDropdownFilter returns this.instance
- // GitLabDropdownRemote returns this.options.instance
- const glDropdown = this.instance || this.options.instance;
- glDropdown.options.processData(term, users, callback);
- }.bind(this));
- },
- processData: function(term, data, callback) {
- let users = data;
-
- // Only show assigned user list when there is no search term
- if ($dropdown.hasClass('js-multiselect') && term.length === 0) {
- const selectedInputs = getSelectedUserInputs();
-
- // Potential duplicate entries when dealing with issue board
- // because issue board is also managed by vue
- const selectedUsers = _.uniq(selectedInputs, false, a => a.value)
- .filter((input) => {
- const userId = parseInt(input.value, 10);
- const inUsersArray = users.find(u => u.id === userId);
-
- return !inUsersArray && userId !== 0;
- })
- .map((input) => {
- const userId = parseInt(input.value, 10);
- const { avatarUrl, avatar_url, name, username } = input.dataset;
- return {
- avatar_url: avatarUrl || avatar_url,
- id: userId,
- name,
- username,
- };
- });
+ };
+ collapsedAssigneeTemplate = _.template(
+ '<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>',
+ );
+ assigneeTemplate = _.template(
+ '<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>',
+ );
+ return $dropdown.glDropdown({
+ showMenuAbove: showMenuAbove,
+ data: function(term, callback) {
+ return _this.users(
+ term,
+ options,
+ function(users) {
+ // GitLabDropdownFilter returns this.instance
+ // GitLabDropdownRemote returns this.options.instance
+ const glDropdown = this.instance || this.options.instance;
+ glDropdown.options.processData(term, users, callback);
+ }.bind(this),
+ );
+ },
+ processData: function(term, data, callback) {
+ let users = data;
+
+ // Only show assigned user list when there is no search term
+ if ($dropdown.hasClass('js-multiselect') && term.length === 0) {
+ const selectedInputs = getSelectedUserInputs();
+
+ // Potential duplicate entries when dealing with issue board
+ // because issue board is also managed by vue
+ const selectedUsers = _.uniq(selectedInputs, false, a => a.value)
+ .filter(input => {
+ const userId = parseInt(input.value, 10);
+ const inUsersArray = users.find(u => u.id === userId);
+
+ return !inUsersArray && userId !== 0;
+ })
+ .map(input => {
+ const userId = parseInt(input.value, 10);
+ const { avatarUrl, avatar_url, name, username } = input.dataset;
+ return {
+ avatar_url: avatarUrl || avatar_url,
+ id: userId,
+ name,
+ username,
+ };
+ });
- users = data.concat(selectedUsers);
- }
+ users = data.concat(selectedUsers);
+ }
- let anyUser;
- let index;
- let len;
- let name;
- let obj;
- let showDivider;
- if (term.length === 0) {
- showDivider = 0;
- if (firstUser) {
- // Move current user to the front of the list
- for (index = 0, len = users.length; index < len; index += 1) {
- obj = users[index];
- if (obj.username === firstUser) {
- users.splice(index, 1);
- users.unshift(obj);
- break;
+ let anyUser;
+ let index;
+ let len;
+ let name;
+ let obj;
+ let showDivider;
+ if (term.length === 0) {
+ showDivider = 0;
+ if (firstUser) {
+ // Move current user to the front of the list
+ for (index = 0, len = users.length; index < len; index += 1) {
+ obj = users[index];
+ if (obj.username === firstUser) {
+ users.splice(index, 1);
+ users.unshift(obj);
+ break;
+ }
}
}
- }
- if (showNullUser) {
- showDivider += 1;
- users.unshift({
- beforeDivider: true,
- name: 'Unassigned',
- id: 0
- });
- }
- if (showAnyUser) {
- showDivider += 1;
- name = showAnyUser;
- if (name === true) {
- name = 'Any User';
+ if (showNullUser) {
+ showDivider += 1;
+ users.unshift({
+ beforeDivider: true,
+ name: 'Unassigned',
+ id: 0,
+ });
+ }
+ if (showAnyUser) {
+ showDivider += 1;
+ name = showAnyUser;
+ if (name === true) {
+ name = 'Any User';
+ }
+ anyUser = {
+ beforeDivider: true,
+ name: name,
+ id: null,
+ };
+ users.unshift(anyUser);
}
- anyUser = {
- beforeDivider: true,
- name: name,
- id: null
- };
- users.unshift(anyUser);
- }
- if (showDivider) {
- users.splice(showDivider, 0, 'divider');
- }
+ if (showDivider) {
+ users.splice(showDivider, 0, 'divider');
+ }
- if ($dropdown.hasClass('js-multiselect')) {
- const selected = getSelected().filter(i => i !== 0);
+ if ($dropdown.hasClass('js-multiselect')) {
+ const selected = getSelected().filter(i => i !== 0);
- if (selected.length > 0) {
- if ($dropdown.data('dropdownHeader')) {
- showDivider += 1;
- users.splice(showDivider, 0, {
- header: $dropdown.data('dropdownHeader'),
- });
- }
+ if (selected.length > 0) {
+ if ($dropdown.data('dropdownHeader')) {
+ showDivider += 1;
+ users.splice(showDivider, 0, {
+ header: $dropdown.data('dropdownHeader'),
+ });
+ }
- const selectedUsers = users
- .filter(u => selected.indexOf(u.id) !== -1)
- .sort((a, b) => a.name > b.name);
+ const selectedUsers = users
+ .filter(u => selected.indexOf(u.id) !== -1)
+ .sort((a, b) => a.name > b.name);
- users = users.filter(u => selected.indexOf(u.id) === -1);
+ users = users.filter(u => selected.indexOf(u.id) === -1);
- selectedUsers.forEach((selectedUser) => {
- showDivider += 1;
- users.splice(showDivider, 0, selectedUser);
- });
+ selectedUsers.forEach(selectedUser => {
+ showDivider += 1;
+ users.splice(showDivider, 0, selectedUser);
+ });
- users.splice(showDivider + 1, 0, 'divider');
+ users.splice(showDivider + 1, 0, 'divider');
+ }
}
}
- }
- callback(users);
- if (showMenuAbove) {
- $dropdown.data('glDropdown').positionMenuAbove();
- }
- },
- filterable: true,
- filterRemote: true,
- search: {
- fields: ['name', 'username']
- },
- selectable: true,
- fieldName: $dropdown.data('fieldName'),
- toggleLabel: function(selected, el, glDropdown) {
- const inputValue = glDropdown.filterInput.val();
-
- if (this.multiSelect && inputValue === '') {
- // Remove non-users from the fullData array
- const users = glDropdown.filteredFullData();
- const callback = glDropdown.parseData.bind(glDropdown);
-
- // Update the data model
- this.processData(inputValue, users, callback);
- }
+ callback(users);
+ if (showMenuAbove) {
+ $dropdown.data('glDropdown').positionMenuAbove();
+ }
+ },
+ filterable: true,
+ filterRemote: true,
+ search: {
+ fields: ['name', 'username'],
+ },
+ selectable: true,
+ fieldName: $dropdown.data('fieldName'),
+ toggleLabel: function(selected, el, glDropdown) {
+ const inputValue = glDropdown.filterInput.val();
+
+ if (this.multiSelect && inputValue === '') {
+ // Remove non-users from the fullData array
+ const users = glDropdown.filteredFullData();
+ const callback = glDropdown.parseData.bind(glDropdown);
+
+ // Update the data model
+ this.processData(inputValue, users, callback);
+ }
- if (this.multiSelect) {
- return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active'));
- }
+ if (this.multiSelect) {
+ return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active'));
+ }
- if (selected && 'id' in selected && $(el).hasClass('is-active')) {
- $dropdown.find('.dropdown-toggle-text').removeClass('is-default');
- if (selected.text) {
- return selected.text;
+ if (selected && 'id' in selected && $(el).hasClass('is-active')) {
+ $dropdown.find('.dropdown-toggle-text').removeClass('is-default');
+ if (selected.text) {
+ return selected.text;
+ } else {
+ return selected.name;
+ }
} else {
- return selected.name;
+ $dropdown.find('.dropdown-toggle-text').addClass('is-default');
+ return defaultLabel;
+ }
+ },
+ defaultLabel: defaultLabel,
+ hidden: function(e) {
+ if ($dropdown.hasClass('js-multiselect')) {
+ emitSidebarEvent('sidebar.saveAssignees');
}
- } else {
- $dropdown.find('.dropdown-toggle-text').addClass('is-default');
- return defaultLabel;
- }
- },
- defaultLabel: defaultLabel,
- hidden: function(e) {
- if ($dropdown.hasClass('js-multiselect')) {
- emitSidebarEvent('sidebar.saveAssignees');
- }
-
- if (!$dropdown.data('alwaysShowSelectbox')) {
- $selectbox.hide();
- // Recalculate where .value is because vue might have changed it
- $block = $selectbox.closest('.block');
- $value = $block.find('.value');
- // display:block overrides the hide-collapse rule
- $value.css('display', '');
- }
- },
- multiSelect: $dropdown.hasClass('js-multiselect'),
- inputMeta: $dropdown.data('inputMeta'),
- clicked: function(options) {
- const { $el, e, isMarking } = options;
- const user = options.selectedObj;
-
- if ($dropdown.hasClass('js-multiselect')) {
- const isActive = $el.hasClass('is-active');
- const previouslySelected = $dropdown.closest('.selectbox')
- .find("input[name='" + ($dropdown.data('fieldName')) + "'][value!=0]");
-
- // Enables support for limiting the number of users selected
- // Automatically removes the first on the list if more users are selected
- checkMaxSelect();
+ if (!$dropdown.data('alwaysShowSelectbox')) {
+ $selectbox.hide();
- if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') {
- // Unassigned selected
- previouslySelected.each((index, element) => {
- const id = parseInt(element.value, 10);
- element.remove();
- });
- emitSidebarEvent('sidebar.removeAllAssignees');
- } else if (isActive) {
- // user selected
- emitSidebarEvent('sidebar.addAssignee', user);
+ // Recalculate where .value is because vue might have changed it
+ $block = $selectbox.closest('.block');
+ $value = $block.find('.value');
+ // display:block overrides the hide-collapse rule
+ $value.css('display', '');
+ }
+ },
+ multiSelect: $dropdown.hasClass('js-multiselect'),
+ inputMeta: $dropdown.data('inputMeta'),
+ clicked: function(options) {
+ const { $el, e, isMarking } = options;
+ const user = options.selectedObj;
- // Remove unassigned selection (if it was previously selected)
- const unassignedSelected = $dropdown.closest('.selectbox')
- .find("input[name='" + ($dropdown.data('fieldName')) + "'][value=0]");
+ if ($dropdown.hasClass('js-multiselect')) {
+ const isActive = $el.hasClass('is-active');
+ const previouslySelected = $dropdown
+ .closest('.selectbox')
+ .find("input[name='" + $dropdown.data('fieldName') + "'][value!=0]");
+
+ // Enables support for limiting the number of users selected
+ // Automatically removes the first on the list if more users are selected
+ checkMaxSelect();
+
+ if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') {
+ // Unassigned selected
+ previouslySelected.each((index, element) => {
+ const id = parseInt(element.value, 10);
+ element.remove();
+ });
+ emitSidebarEvent('sidebar.removeAllAssignees');
+ } else if (isActive) {
+ // user selected
+ emitSidebarEvent('sidebar.addAssignee', user);
+
+ // Remove unassigned selection (if it was previously selected)
+ const unassignedSelected = $dropdown
+ .closest('.selectbox')
+ .find("input[name='" + $dropdown.data('fieldName') + "'][value=0]");
+
+ if (unassignedSelected) {
+ unassignedSelected.remove();
+ }
+ } else {
+ if (previouslySelected.length === 0) {
+ // Select unassigned because there is no more selected users
+ this.addInput($dropdown.data('fieldName'), 0, {});
+ }
- if (unassignedSelected) {
- unassignedSelected.remove();
- }
- } else {
- if (previouslySelected.length === 0) {
- // Select unassigned because there is no more selected users
- this.addInput($dropdown.data('fieldName'), 0, {});
+ // User unselected
+ emitSidebarEvent('sidebar.removeAssignee', user);
}
- // User unselected
- emitSidebarEvent('sidebar.removeAssignee', user);
+ if (getSelected().find(u => u === gon.current_user_id)) {
+ $('.assign-to-me-link').hide();
+ } else {
+ $('.assign-to-me-link').show();
+ }
}
- if (getSelected().find(u => u === gon.current_user_id)) {
- $('.assign-to-me-link').hide();
- } else {
- $('.assign-to-me-link').show();
+ var isIssueIndex, isMRIndex, page, selected;
+ page = $('body').attr('data-page');
+ isIssueIndex = page === 'projects:issues:index';
+ isMRIndex = page === page && page === 'projects:merge_requests:index';
+ if (
+ $dropdown.hasClass('js-filter-bulk-update') ||
+ $dropdown.hasClass('js-issuable-form-dropdown')
+ ) {
+ e.preventDefault();
+
+ const isSelecting = user.id !== selectedId;
+ selectedId = isSelecting ? user.id : selectedIdDefault;
+
+ if (selectedId === gon.current_user_id) {
+ $('.assign-to-me-link').hide();
+ } else {
+ $('.assign-to-me-link').show();
+ }
+ return;
+ }
+ if ($el.closest('.add-issues-modal').length) {
+ ModalStore.store.filter[$dropdown.data('fieldName')] = user.id;
+ } else if (handleClick) {
+ e.preventDefault();
+ handleClick(user, isMarking);
+ } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
+ return Issuable.filterResults($dropdown.closest('form'));
+ } else if ($dropdown.hasClass('js-filter-submit')) {
+ return $dropdown.closest('form').submit();
+ } else if (!$dropdown.hasClass('js-multiselect')) {
+ selected = $dropdown
+ .closest('.selectbox')
+ .find("input[name='" + $dropdown.data('fieldName') + "']")
+ .val();
+ return assignTo(selected);
}
- }
- var isIssueIndex, isMRIndex, page, selected;
- page = $('body').attr('data-page');
- isIssueIndex = page === 'projects:issues:index';
- isMRIndex = (page === page && page === 'projects:merge_requests:index');
- if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
- e.preventDefault();
+ // Automatically close dropdown after assignee is selected
+ // since CE has no multiple assignees
+ // EE does not have a max-select
+ if (
+ $dropdown.data('maxSelect') &&
+ getSelected().length === $dropdown.data('maxSelect')
+ ) {
+ // Close the dropdown
+ $dropdown.dropdown('toggle');
+ }
+ },
+ id: function(user) {
+ return user.id;
+ },
+ opened: function(e) {
+ const $el = $(e.currentTarget);
+ const selected = getSelected();
+ if ($dropdown.hasClass('js-issue-board-sidebar') && selected.length === 0) {
+ this.addInput($dropdown.data('fieldName'), 0, {});
+ }
+ $el.find('.is-active').removeClass('is-active');
- const isSelecting = (user.id !== selectedId);
- selectedId = isSelecting ? user.id : selectedIdDefault;
+ function highlightSelected(id) {
+ $el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active');
+ }
- if (selectedId === gon.current_user_id) {
- $('.assign-to-me-link').hide();
+ if (selected.length > 0) {
+ getSelected().forEach(selectedId => highlightSelected(selectedId));
+ } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
+ highlightSelected(0);
} else {
- $('.assign-to-me-link').show();
+ highlightSelected(selectedId);
}
- return;
- }
- if ($el.closest('.add-issues-modal').length) {
- ModalStore.store.filter[$dropdown.data('fieldName')] = user.id;
- } else if (handleClick) {
- e.preventDefault();
- handleClick(user, isMarking);
- } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
- return Issuable.filterResults($dropdown.closest('form'));
- } else if ($dropdown.hasClass('js-filter-submit')) {
- return $dropdown.closest('form').submit();
- } else if (!$dropdown.hasClass('js-multiselect')) {
- selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('fieldName')) + "']").val();
- return assignTo(selected);
- }
-
- // Automatically close dropdown after assignee is selected
- // since CE has no multiple assignees
- // EE does not have a max-select
- if ($dropdown.data('maxSelect') &&
- getSelected().length === $dropdown.data('maxSelect')) {
- // Close the dropdown
- $dropdown.dropdown('toggle');
- }
- },
- id: function (user) {
- return user.id;
- },
- opened: function(e) {
- const $el = $(e.currentTarget);
- const selected = getSelected();
- if ($dropdown.hasClass('js-issue-board-sidebar') && selected.length === 0) {
- this.addInput($dropdown.data('fieldName'), 0, {});
- }
- $el.find('.is-active').removeClass('is-active');
-
- function highlightSelected(id) {
- $el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active');
- }
+ },
+ updateLabel: $dropdown.data('dropdownTitle'),
+ renderRow: function(user) {
+ var avatar, img, listClosingTags, listWithName, listWithUserName, username;
+ username = user.username ? '@' + user.username : '';
+ avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
- if (selected.length > 0) {
- getSelected().forEach(selectedId => highlightSelected(selectedId));
- } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
- highlightSelected(0);
- } else {
- highlightSelected(selectedId);
- }
- },
- updateLabel: $dropdown.data('dropdownTitle'),
- renderRow: function(user) {
- var avatar, img, listClosingTags, listWithName, listWithUserName, username;
- username = user.username ? "@" + user.username : "";
- avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
+ let selected = false;
- let selected = false;
+ if (this.multiSelect) {
+ selected = getSelected().find(u => user.id === u);
- if (this.multiSelect) {
- selected = getSelected().find(u => user.id === u);
+ const { fieldName } = this;
+ const field = $dropdown
+ .closest('.selectbox')
+ .find("input[name='" + fieldName + "'][value='" + user.id + "']");
- const { fieldName } = this;
- const field = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "'][value='" + user.id + "']");
-
- if (field.length) {
- selected = true;
+ if (field.length) {
+ selected = true;
+ }
+ } else {
+ selected = user.id === selectedId;
}
- } else {
- selected = user.id === selectedId;
- }
- img = "";
- if (user.beforeDivider != null) {
- `<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${_.escape(user.name)}</a></li>`;
- } else {
- img = "<img src='" + avatar + "' class='avatar avatar-inline' width='32' />";
- }
+ img = '';
+ if (user.beforeDivider != null) {
+ `<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${_.escape(
+ user.name,
+ )}</a></li>`;
+ } else {
+ img = "<img src='" + avatar + "' class='avatar avatar-inline' width='32' />";
+ }
- return `
+ return `
<li data-user-id=${user.id}>
<a href='#' class='dropdown-menu-user-link ${selected === true ? 'is-active' : ''}'>
${img}
@@ -528,114 +574,117 @@ function UsersSelect(currentUser, els, options = {}) {
</a>
</li>
`;
- }
- });
- };
- })(this));
- $('.ajax-users-select').each((function(_this) {
- return function(i, select) {
- var firstUser, showAnyUser, showEmailUser, showNullUser;
- var options = {};
- options.skipLdap = $(select).hasClass('skip_ldap');
- options.projectId = $(select).data('projectId');
- options.groupId = $(select).data('groupId');
- options.showCurrentUser = $(select).data('currentUser');
- options.authorId = $(select).data('authorId');
- options.skipUsers = $(select).data('skipUsers');
- showNullUser = $(select).data('nullUser');
- showAnyUser = $(select).data('anyUser');
- showEmailUser = $(select).data('emailUser');
- firstUser = $(select).data('firstUser');
- return $(select).select2({
- placeholder: "Search for a user",
- multiple: $(select).hasClass('multiselect'),
- minimumInputLength: 0,
- query: function(query) {
- return _this.users(query.term, options, function(users) {
- var anyUser, data, emailUser, index, len, name, nullUser, obj, ref;
- data = {
- results: users
- };
- if (query.term.length === 0) {
- if (firstUser) {
- // Move current user to the front of the list
- ref = data.results;
-
- for (index = 0, len = ref.length; index < len; index += 1) {
- obj = ref[index];
- if (obj.username === firstUser) {
- data.results.splice(index, 1);
- data.results.unshift(obj);
- break;
+ },
+ });
+ };
+ })(this),
+ );
+ $('.ajax-users-select').each(
+ (function(_this) {
+ return function(i, select) {
+ var firstUser, showAnyUser, showEmailUser, showNullUser;
+ var options = {};
+ options.skipLdap = $(select).hasClass('skip_ldap');
+ options.projectId = $(select).data('projectId');
+ options.groupId = $(select).data('groupId');
+ options.showCurrentUser = $(select).data('currentUser');
+ options.authorId = $(select).data('authorId');
+ options.skipUsers = $(select).data('skipUsers');
+ showNullUser = $(select).data('nullUser');
+ showAnyUser = $(select).data('anyUser');
+ showEmailUser = $(select).data('emailUser');
+ firstUser = $(select).data('firstUser');
+ return $(select).select2({
+ placeholder: 'Search for a user',
+ multiple: $(select).hasClass('multiselect'),
+ minimumInputLength: 0,
+ query: function(query) {
+ return _this.users(query.term, options, function(users) {
+ var anyUser, data, emailUser, index, len, name, nullUser, obj, ref;
+ data = {
+ results: users,
+ };
+ if (query.term.length === 0) {
+ if (firstUser) {
+ // Move current user to the front of the list
+ ref = data.results;
+
+ for (index = 0, len = ref.length; index < len; index += 1) {
+ obj = ref[index];
+ if (obj.username === firstUser) {
+ data.results.splice(index, 1);
+ data.results.unshift(obj);
+ break;
+ }
}
}
- }
- if (showNullUser) {
- nullUser = {
- name: 'Unassigned',
- id: 0
- };
- data.results.unshift(nullUser);
- }
- if (showAnyUser) {
- name = showAnyUser;
- if (name === true) {
- name = 'Any User';
+ if (showNullUser) {
+ nullUser = {
+ name: 'Unassigned',
+ id: 0,
+ };
+ data.results.unshift(nullUser);
}
- anyUser = {
- name: name,
- id: null
+ if (showAnyUser) {
+ name = showAnyUser;
+ if (name === true) {
+ name = 'Any User';
+ }
+ anyUser = {
+ name: name,
+ id: null,
+ };
+ data.results.unshift(anyUser);
+ }
+ }
+ if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
+ var trimmed = query.term.trim();
+ emailUser = {
+ name: 'Invite "' + trimmed + '" by email',
+ username: trimmed,
+ id: trimmed,
+ invite: true,
};
- data.results.unshift(anyUser);
+ data.results.unshift(emailUser);
}
- }
- if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
- var trimmed = query.term.trim();
- emailUser = {
- name: "Invite \"" + trimmed + "\" by email",
- username: trimmed,
- id: trimmed,
- invite: true
- };
- data.results.unshift(emailUser);
- }
- return query.callback(data);
- });
- },
- initSelection: function() {
- var args;
- args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
- return _this.initSelection.apply(_this, args);
- },
- formatResult: function() {
- var args;
- args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
- return _this.formatResult.apply(_this, args);
- },
- formatSelection: function() {
- var args;
- args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
- return _this.formatSelection.apply(_this, args);
- },
- dropdownCssClass: "ajax-users-dropdown",
- // we do not want to escape markup since we are displaying html in results
- escapeMarkup: function(m) {
- return m;
- }
- });
- };
- })(this));
+ return query.callback(data);
+ });
+ },
+ initSelection: function() {
+ var args;
+ args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
+ return _this.initSelection.apply(_this, args);
+ },
+ formatResult: function() {
+ var args;
+ args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
+ return _this.formatResult.apply(_this, args);
+ },
+ formatSelection: function() {
+ var args;
+ args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
+ return _this.formatSelection.apply(_this, args);
+ },
+ dropdownCssClass: 'ajax-users-dropdown',
+ // we do not want to escape markup since we are displaying html in results
+ escapeMarkup: function(m) {
+ return m;
+ },
+ });
+ };
+ })(this),
+ );
}
UsersSelect.prototype.initSelection = function(element, callback) {
var id, nullUser;
id = $(element).val();
- if (id === "0") {
+ if (id === '0') {
nullUser = {
- name: 'Unassigned'
+ name: 'Unassigned',
};
return callback(nullUser);
- } else if (id !== "") {
+ } else if (id !== '') {
return this.user(id, callback);
}
};
@@ -647,7 +696,17 @@ UsersSelect.prototype.formatResult = function(user) {
} else {
avatar = gon.default_avatar_url;
}
- return "<div class='user-result " + (!user.username ? 'no-username' : void 0) + "'> <div class='user-image'><img class='avatar avatar-inline s32' src='" + avatar + "'></div> <div class='user-name dropdown-menu-user-full-name'>" + _.escape(user.name) + "</div> <div class='user-username dropdown-menu-user-username'>" + (!user.invite ? "@" + _.escape(user.username) : "") + "</div> </div>";
+ return (
+ "<div class='user-result " +
+ (!user.username ? 'no-username' : void 0) +
+ "'> <div class='user-image'><img class='avatar avatar-inline s32' src='" +
+ avatar +
+ "'></div> <div class='user-name dropdown-menu-user-full-name'>" +
+ _.escape(user.name) +
+ "</div> <div class='user-username dropdown-menu-user-username'>" +
+ (!user.invite ? '@' + _.escape(user.username) : '') +
+ '</div> </div>'
+ );
};
UsersSelect.prototype.formatSelection = function(user) {
@@ -662,10 +721,9 @@ UsersSelect.prototype.user = function(user_id, callback) {
var url;
url = this.buildUrl(this.userPath);
url = url.replace(':id', user_id);
- return axios.get(url)
- .then(({ data }) => {
- callback(data);
- });
+ return axios.get(url).then(({ data }) => {
+ callback(data);
+ });
};
// Return users list. Filtered by query
@@ -682,12 +740,11 @@ UsersSelect.prototype.users = function(query, options, callback) {
todo_state_filter: options.todoStateFilter || null,
current_user: options.showCurrentUser || null,
author_id: options.authorId || null,
- skip_users: options.skipUsers || null
+ skip_users: options.skipUsers || null,
};
- return axios.get(url, { params })
- .then(({ data }) => {
- callback(data);
- });
+ return axios.get(url, { params }).then(({ data }) => {
+ callback(data);
+ });
};
UsersSelect.prototype.buildUrl = function(url) {
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 9161f703697..6c87287a4c4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -1,6 +1,7 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
+import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue';
import timeagoMixin from '../../vue_shared/mixins/timeago';
import tooltip from '../../vue_shared/directives/tooltip';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
@@ -18,6 +19,7 @@ export default {
StatusIcon,
Icon,
TooltipOnTruncate,
+ FilteredSearchDropdown,
},
directives: {
tooltip,
@@ -30,8 +32,10 @@ export default {
},
},
data() {
+ const features = window.gon.features || {};
return {
isStopping: false,
+ enableCiEnvironmentsStatusChanges: features.ciEnvironmentsStatusChanges,
};
},
computed: {
@@ -118,18 +122,65 @@ export default {
/>
</div>
<div>
- <a
- v-if="hasExternalUrls"
- :href="deployment.external_url"
- target="_blank"
- rel="noopener noreferrer nofollow"
- class="deploy-link js-deploy-url btn btn-default btn-sm inline"
- >
- <span>
- View app
- <icon name="external-link" />
- </span>
- </a>
+ <template v-if="hasExternalUrls">
+ <filtered-search-dropdown
+ v-if="enableCiEnvironmentsStatusChanges"
+ class="js-mr-wigdet-deployment-dropdown inline"
+ :items="deployment.changes"
+ :main-action-link="deployment.external_url"
+ filter-key="path"
+ >
+ <template
+ slot="mainAction"
+ slot-scope="slotProps"
+ >
+ <a
+ :href="deployment.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="deploy-link js-deploy-url inline"
+ :class="slotProps.className"
+ >
+ <span>
+ {{ __('View app') }}
+ <icon name="external-link" />
+ </span>
+ </a>
+ </template>
+
+ <template
+ slot="result"
+ slot-scope="slotProps"
+ >
+ <a
+ :href="slotProps.result.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="menu-item"
+ >
+ <strong class="str-truncated-100 append-bottom-0 d-block">
+ {{ slotProps.result.path }}
+ </strong>
+
+ <p class="text-secondary str-truncated-100 append-bottom-0 d-block">
+ {{ slotProps.result.external_url }}
+ </p>
+ </a>
+ </template>
+ </filtered-search-dropdown>
+ <a
+ v-else
+ :href="deployment.external_url"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline"
+ >
+ <span>
+ {{ __('View app') }}
+ <icon name="external-link" />
+ </span>
+ </a>
+ </template>
<loading-button
v-if="deployment.stop_url"
:loading="isStopping"
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 7ac3fedb2e3..8180f13a7cb 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
@@ -112,7 +112,8 @@ export default {
eventHub.$on('mr.discussion.updated', this.checkStatus);
},
mounted() {
- this.handleMounted();
+ this.setFaviconHelper();
+ this.initDeploymentsPolling();
},
beforeDestroy() {
eventHub.$off('mr.discussion.updated', this.checkStatus);
@@ -250,10 +251,6 @@ export default {
this.stopPolling();
});
},
- handleMounted() {
- this.setFaviconHelper();
- this.initDeploymentsPolling();
- },
},
};
</script>
@@ -275,12 +272,13 @@ 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"
+ class="js-reports-container"
+ :endpoint="mr.testResultsPath"
+ />
+
<div class="mr-widget-section">
<component
:is="componentName"
diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
index a2518e2a611..c60052fec50 100644
--- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
+++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue
@@ -43,7 +43,7 @@ export default {
computed: {
cssClass() {
const className = this.status.group;
- return className ? `ci-status ci-${className}` : 'ci-status';
+ return className ? `ci-status ci-${className} qa-status-badge` : 'ci-status qa-status-badge';
},
},
};
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 19611b14be4..bffaa096210 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
@@ -549,6 +549,7 @@ const fileNameIcons = {
jenkinsfile: 'jenkins',
'firebase.json': 'firebase',
'.firebaserc': 'firebase',
+ Rakefile: 'ruby',
'rollup.config.js': 'rollup',
'rollup.config.ts': 'rollup',
'rollup-config.js': 'rollup',
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 36a345130c0..2d89a156117 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -34,10 +34,21 @@ export default {
required: false,
default: false,
},
+ displayTextKey: {
+ type: String,
+ required: false,
+ default: 'name',
+ },
+ shouldTruncateStart: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
mouseOver: false,
+ truncateStart: 0,
};
},
computed: {
@@ -60,6 +71,15 @@ export default {
'is-open': this.file.opened,
};
},
+ outputText() {
+ const text = this.file[this.displayTextKey];
+
+ if (this.truncateStart === 0) {
+ return text;
+ }
+
+ return `...${text.substring(this.truncateStart, text.length)}`;
+ },
},
watch: {
'file.active': function fileActiveWatch(active) {
@@ -72,6 +92,15 @@ export default {
if (this.hasPathAtCurrentRoute()) {
this.scrollIntoView(true);
}
+
+ if (this.shouldTruncateStart) {
+ const { scrollWidth, offsetWidth } = this.$refs.textOutput;
+ const textOverflow = scrollWidth - offsetWidth;
+
+ if (textOverflow > 0) {
+ this.truncateStart = Math.ceil(textOverflow / 5) + 3;
+ }
+ }
},
methods: {
toggleTreeOpen(path) {
@@ -139,6 +168,7 @@ export default {
class="file-row-name-container"
>
<span
+ ref="textOutput"
:style="levelIndentation"
class="file-row-name str-truncated"
>
@@ -156,7 +186,7 @@ export default {
:size="16"
class="append-right-5"
/>
- {{ file.name }}
+ {{ outputText }}
</span>
<component
:is="extraComponent"
@@ -175,6 +205,8 @@ export default {
:hide-extra-on-tree="hideExtraOnTree"
:extra-component="extraComponent"
:show-changed-icon="showChangedIcon"
+ :display-text-key="displayTextKey"
+ :should-truncate-start="shouldTruncateStart"
@toggleTreeOpen="toggleTreeOpen"
@clickFile="clickedFile"
/>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
new file mode 100644
index 00000000000..460fa6ad72e
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue
@@ -0,0 +1,143 @@
+<script>
+import $ from 'jquery';
+import Icon from '~/vue_shared/components/icon.vue';
+/**
+ * Renders a split dropdown with
+ * an input that allows to search through the given
+ * array of options.
+ */
+export default {
+ name: 'FilteredSearchDropdown',
+ components: {
+ Icon,
+ },
+ props: {
+ title: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ buttonType: {
+ required: false,
+ validator: value =>
+ ['primary', 'default', 'secondary', 'success', 'info', 'warning', 'danger'].indexOf(
+ value,
+ ) !== -1,
+ default: 'default',
+ },
+ size: {
+ required: false,
+ type: String,
+ default: 'sm',
+ },
+ items: {
+ type: Array,
+ required: true,
+ },
+ visibleItems: {
+ type: Number,
+ required: false,
+ default: 5,
+ },
+ filterKey: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ filter: '',
+ };
+ },
+ computed: {
+ className() {
+ return `btn btn-${this.buttonType} btn-${this.size}`;
+ },
+ filteredResults() {
+ if (this.filter !== '') {
+ return this.items.filter(
+ item => item[this.filterKey] && item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()),
+ );
+ }
+
+ return this.items.slice(0, this.visibleItems);
+ }
+ },
+ mounted() {
+ /**
+ * Resets the filter every time the user closes the dropdown
+ */
+ $(this.$el)
+ .on('shown.bs.dropdown', () => {
+ this.$nextTick(() => this.$refs.searchInput.focus());
+ })
+ .on('hidden.bs.dropdown', () => {
+ this.filter = '';
+ });
+ },
+};
+</script>
+<template>
+ <div class="dropdown">
+ <div class="btn-group">
+ <slot
+ name="mainAction"
+ :class-name="className"
+ >
+ <button
+ type="button"
+ :class="className"
+ >
+ {{ title }}
+ </button>
+ </slot>
+
+ <button
+ type="button"
+ :class="className"
+ class="dropdown-toggle dropdown-toggle-split"
+ data-toggle="dropdown"
+ aria-haspopup="true"
+ aria-expanded="false"
+ aria-label="Expand dropdown"
+ >
+ <icon
+ name="angle-down"
+ :size="12"
+ />
+ </button>
+ <div class="dropdown-menu dropdown-menu-right">
+ <div class="dropdown-input">
+ <input
+ ref="searchInput"
+ v-model="filter"
+ type="search"
+ placeholder="Filter"
+ class="js-filtered-dropdown-input dropdown-input-field"
+ />
+ <icon
+ class="dropdown-input-search"
+ name="search"
+ />
+ </div>
+
+ <div class="dropdown-content">
+ <ul>
+ <li
+ v-for="(result, i) in filteredResults"
+ :key="i"
+ class="js-filtered-dropdown-result"
+ >
+ <slot
+ name="result"
+ :result="result"
+ >
+ {{ result[filterKey] }}
+ </slot>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/gl_countdown.vue b/app/assets/javascripts/vue_shared/components/gl_countdown.vue
new file mode 100644
index 00000000000..9327a2a4a6c
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/gl_countdown.vue
@@ -0,0 +1,49 @@
+<script>
+import { calculateRemainingMilliseconds, formatTime } from '~/lib/utils/datetime_utility';
+
+/**
+ * Counts down to a given end date.
+ */
+export default {
+ props: {
+ endDateString: {
+ type: String,
+ required: true,
+ validator(value) {
+ return !Number.isNaN(new Date(value).getTime());
+ },
+ },
+ },
+
+ data() {
+ return {
+ remainingTime: formatTime(0),
+ countdownUpdateIntervalId: null,
+ };
+ },
+
+ mounted() {
+ const updateRemainingTime = () => {
+ const remainingMilliseconds = calculateRemainingMilliseconds(this.endDateString);
+ this.remainingTime = formatTime(remainingMilliseconds);
+ };
+
+ updateRemainingTime();
+ this.countdownUpdateIntervalId = window.setInterval(updateRemainingTime, 1000);
+ },
+
+ beforeDestroy() {
+ window.clearInterval(this.countdownUpdateIntervalId);
+ },
+};
+</script>
+
+<template>
+ <time
+ v-gl-tooltip
+ :datetime="endDateString"
+ :title="endDateString"
+ >
+ {{ remainingTime }}
+ </time>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index b371b6adf7e..aee88cae48d 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -69,6 +69,9 @@ export default {
onClickAction(action) {
this.$emit('actionClicked', action);
},
+ onClickSidebarButton() {
+ this.$emit('clickedSidebarButton');
+ },
},
};
</script>
@@ -161,21 +164,21 @@ export default {
</i>
</button>
</template>
- <button
- v-if="hasSidebarButton"
- id="toggleSidebar"
- type="button"
- class="btn btn-default d-block d-sm-none d-md-none
+ </section>
+ <button
+ v-if="hasSidebarButton"
+ id="toggleSidebar"
+ type="button"
+ class="btn btn-default d-block d-sm-none
sidebar-toggle-btn js-sidebar-build-toggle js-sidebar-build-toggle-header"
- aria-label="Toggle Sidebar"
+ @click="onClickSidebarButton"
+ >
+ <i
+ class="fa fa-angle-double-left"
+ aria-hidden="true"
+ aria-labelledby="toggleSidebar"
>
- <i
- class="fa fa-angle-double-left"
- aria-hidden="true"
- aria-labelledby="toggleSidebar"
- >
- </i>
- </button>
- </section>
+ </i>
+ </button>
</header>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 704adf7864f..3ddb39730c4 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,16 +1,16 @@
<script>
import $ from 'jquery';
-import tooltip from '../../directives/tooltip';
-import toolbarButton from './toolbar_button.vue';
-import icon from '../icon.vue';
+import Tooltip from '../../directives/tooltip';
+import ToolbarButton from './toolbar_button.vue';
+import Icon from '../icon.vue';
export default {
directives: {
- tooltip,
+ Tooltip,
},
components: {
- toolbarButton,
- icon,
+ ToolbarButton,
+ Icon,
},
props: {
previewMarkdown: {
@@ -68,27 +68,27 @@ export default {
:class="{ active: !previewMarkdown }"
class="md-header-tab"
>
- <a
+ <button
class="js-write-link"
- href="#md-write-holder"
tabindex="-1"
- @click.prevent="writeMarkdownTab($event)"
+ type="button"
+ @click="writeMarkdownTab($event)"
>
Write
- </a>
+ </button>
</li>
<li
:class="{ active: previewMarkdown }"
class="md-header-tab"
>
- <a
+ <button
class="js-preview-link js-md-preview-button"
- href="#md-preview-holder"
tabindex="-1"
- @click.prevent="previewMarkdownTab($event)"
+ type="button"
+ @click="previewMarkdownTab($event)"
>
Preview
- </a>
+ </button>
</li>
<li
:class="{ active: !previewMarkdown }"
diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue
index 782d8e3abf6..26c99aecae4 100644
--- a/app/assets/javascripts/vue_shared/components/pikaday.vue
+++ b/app/assets/javascripts/vue_shared/components/pikaday.vue
@@ -1,6 +1,6 @@
<script>
import Pikaday from 'pikaday';
-import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix';
+import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility';
export default {
name: 'DatePicker',
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index bdb2351c344..e98c4d7bf7a 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -47,16 +47,26 @@ export default class ZenMode {
e.preventDefault();
return $(e.currentTarget).trigger('zen_mode:leave');
});
- $(document).on('zen_mode:enter', (function(_this) {
- return function(e) {
- return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop'));
- };
- })(this));
- $(document).on('zen_mode:leave', (function(_this) {
- return function(e) {
- return _this.exit();
- };
- })(this));
+ $(document).on(
+ 'zen_mode:enter',
+ (function(_this) {
+ return function(e) {
+ return _this.enter(
+ $(e.target)
+ .closest('.md-area')
+ .find('.zen-backdrop'),
+ );
+ };
+ })(this),
+ );
+ $(document).on(
+ 'zen_mode:leave',
+ (function(_this) {
+ return function(e) {
+ return _this.exit();
+ };
+ })(this),
+ );
$(document).on('keydown', function(e) {
// Esc
if (e.keyCode === 27) {
@@ -93,7 +103,7 @@ export default class ZenMode {
scrollTo(zen_area) {
return $.scrollTo(zen_area, 0, {
- offset: -150
+ offset: -150,
});
}
}
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index af73954bd2e..1e00aa4ff7e 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -238,10 +238,6 @@ h3.popover-header {
}
.card {
- .card-title {
- margin-bottom: 0;
- }
-
&.card-without-border {
@extend .border-0;
}
@@ -255,13 +251,6 @@ h3.popover-header {
}
}
-.card-header {
- h3.card-title,
- h4.card-title {
- margin-top: 0;
- }
-}
-
.nav-tabs {
// Override bootstrap's default border
border-bottom: 0;
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index 4ffb3e9ab42..4041f2b4479 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -51,6 +51,7 @@
@import 'framework/blank';
@import 'framework/wells';
@import 'framework/page_header';
+@import 'framework/page_title';
@import 'framework/awards';
@import 'framework/images';
@import 'framework/broadcast_messages';
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 3c9505a21d6..fa753b13e5f 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -334,6 +334,14 @@ img.emoji {
}
}
+.outline-0 {
+ outline: 0;
+
+ &:focus {
+ outline: 0;
+ }
+}
+
/** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; }
.prepend-top-2 { margin-top: 2px; }
@@ -369,3 +377,5 @@ img.emoji {
.flex-align-self-center { align-self: center; }
.flex-grow { flex-grow: 1; }
.flex-no-shrink { flex-shrink: 0; }
+.mw-460 { max-width: 460px; }
+.ws-initial { white-space: initial; }
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index 50ebc6d0dd1..b8bb9e1e07b 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -161,6 +161,7 @@
.nav-links li {
&.active a,
+ &.md-header-tab.active button,
a.active {
border-bottom: 2px solid $active-tab-border;
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index c430009bfe0..d1ce3a582bb 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -530,9 +530,6 @@
.header-user {
&.show .dropdown-menu {
- width: auto;
- min-width: unset;
- max-height: 323px;
margin-top: 4px;
color: $gl-text-color;
left: auto;
@@ -544,15 +541,19 @@
display: block;
}
- .user-status-emoji {
+ .user-status {
margin-right: 0;
- display: block;
- vertical-align: text-top;
- max-width: 148px;
- font-size: 12px;
+ max-width: 240px;
+ font-size: $gl-font-size-small;
gl-emoji {
- font-size: $gl-font-size;
+ font-size: $gl-font-size-small;
+ }
+
+ .user-status-emoji {
+ gl-emoji {
+ font-size: $gl-font-size;
+ }
}
}
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index 554e2b6720a..3142f94b192 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -72,6 +72,7 @@
.md-header-tab {
@include media-breakpoint-down(xs) {
flex: 1;
+ flex-direction: column;
width: 100%;
border-bottom: 1px solid $border-color;
text-align: center;
diff --git a/app/assets/stylesheets/framework/page_title.scss b/app/assets/stylesheets/framework/page_title.scss
new file mode 100644
index 00000000000..e8302953a63
--- /dev/null
+++ b/app/assets/stylesheets/framework/page_title.scss
@@ -0,0 +1,18 @@
+.page-title-holder {
+ @extend .d-flex;
+ @extend .align-items-center;
+
+ padding-top: $gl-padding-top;
+ border-bottom: 1px solid $border-color;
+
+ .page-title {
+ margin: $gl-padding 0;
+ font-size: 1.75em;
+ font-weight: $gl-font-weight-bold;
+ color: $gl-text-color;
+ }
+
+ .page-title-controls {
+ margin-left: auto;
+ }
+}
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 5ca4d944d73..3a117106cff 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -53,8 +53,3 @@
margin-top: $gl-padding;
}
}
-
-.card-title {
- font-size: inherit;
- line-height: inherit;
-}
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index 8bab8cf36b1..f47dfe1b563 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -8,15 +8,17 @@
height: auto;
border-bottom: 1px solid $border-color;
- li {
+ li:not(.md-header-toolbar) {
display: flex;
- a {
+ a,
+ button {
padding: $gl-btn-padding;
padding-bottom: 11px;
font-size: 14px;
line-height: 28px;
color: $gl-text-color-secondary;
+ border: 0;
border-bottom: 2px solid transparent;
white-space: nowrap;
@@ -33,7 +35,13 @@
}
}
+ button {
+ padding-top: 0;
+ background-color: transparent;
+ }
+
&.active a,
+ &.active button,
a.active {
color: $black;
font-weight: $gl-font-weight-bold;
@@ -42,6 +50,10 @@
color: $black;
}
}
+
+ &.md-header-tab button {
+ line-height: 19px;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/terms.scss b/app/assets/stylesheets/framework/terms.scss
index 7cda674e5c8..3f4be8829d7 100644
--- a/app/assets/stylesheets/framework/terms.scss
+++ b/app/assets/stylesheets/framework/terms.scss
@@ -19,17 +19,12 @@
justify-content: space-between;
line-height: $line-height-base;
- .card-title {
+ .logo-text {
+ width: 55px;
+ height: 24px;
display: flex;
- align-items: center;
-
- .logo-text {
- width: 55px;
- height: 24px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- }
+ flex-direction: column;
+ justify-content: center;
}
.navbar-collapse {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 0fde6e18cc7..ad66a0365ed 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -194,6 +194,7 @@ $well-light-text-color: #5b6169;
* Text
*/
$gl-font-size: 14px;
+$gl-font-size-small: 12px;
$gl-font-weight-normal: 400;
$gl-font-weight-bold: 600;
$gl-text-color: #2e2e2e;
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index ed877f625b5..227f49ec595 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -117,7 +117,6 @@
.controllers {
display: flex;
- font-size: 15px;
justify-content: center;
align-items: center;
@@ -179,6 +178,7 @@
.build-loader-animation {
@include build-loader-animation;
+ float: left;
}
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 8d884ad6891..52c91266ff4 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1027,8 +1027,12 @@
overflow-x: auto;
}
-.tree-list-search .form-control {
- padding-left: 30px;
+.tree-list-search {
+ flex: 0 0 34px;
+
+ .form-control {
+ padding-left: 30px;
+ }
}
.tree-list-icon {
@@ -1063,3 +1067,9 @@
}
}
}
+
+.tree-list-view-toggle {
+ svg {
+ top: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 0f95fb911e1..8ea34f5d19d 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -185,7 +185,17 @@ ul.related-merge-requests > li {
}
.new-branch-col {
- padding-top: 10px;
+ font-size: 0;
+
+ .discussion-filter-container {
+ &:not(:only-child) {
+ margin-right: $gl-padding-8;
+ }
+
+ @include media-breakpoint-down(md) {
+ margin-top: $gl-padding-8;
+ }
+ }
}
.create-mr-dropdown-wrap {
@@ -205,6 +215,10 @@ ul.related-merge-requests > li {
.btn-group:not(.hidden) {
display: flex;
+
+ @include media-breakpoint-down(md) {
+ margin-top: $gl-padding-8;
+ }
}
.js-create-merge-request {
@@ -251,7 +265,6 @@ ul.related-merge-requests > li {
.new-branch-col {
padding-top: 0;
- text-align: right;
align-self: center;
}
@@ -262,3 +275,9 @@ ul.related-merge-requests > li {
}
}
}
+
+@include media-breakpoint-up(lg) {
+ .new-branch-col {
+ text-align: right;
+ }
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 895db89f289..fa6afbf81de 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -47,7 +47,6 @@
}
}
-
.mr-widget-heading {
position: relative;
border: 1px solid $border-color;
@@ -454,7 +453,7 @@
.mr-list {
.merge-request {
- padding: 10px 0 10px 15px;
+ padding: 10px 0 10px 15px;
position: relative;
display: -webkit-flex;
display: flex;
@@ -468,7 +467,6 @@
margin-bottom: 2px;
.ci-status-link {
-
svg {
height: 16px;
width: 16px;
@@ -698,7 +696,6 @@
.table-holder {
.ci-table {
-
th {
background-color: $white-light;
color: $gl-text-color-secondary;
@@ -775,7 +772,7 @@
&.affix {
left: 0;
- transition: right .15s;
+ transition: right 0.15s;
@include media-breakpoint-down(xs) {
right: 0;
@@ -821,9 +818,17 @@
display: flex;
justify-content: space-between;
- @include media-breakpoint-down(xs) {
+ @include media-breakpoint-down(md) {
flex-direction: column-reverse;
}
+
+ .discussion-filter-container {
+ margin-top: $gl-padding-8;
+
+ &:not(:only-child) {
+ padding-right: $gl-padding-8;
+ }
+ }
}
.limit-container-width:not(.container-limited) {
@@ -884,7 +889,7 @@
}
> *:not(:last-child) {
- margin-right: .3em;
+ margin-right: 0.3em;
}
svg {
@@ -907,6 +912,10 @@
.btn svg {
fill: $theme-gray-700;
}
+
+ .dropdown-menu {
+ width: 400px;
+ }
}
// Hack alert: we've rewritten `btn` class in a way that
@@ -917,7 +926,7 @@
&[disabled] {
cursor: not-allowed;
box-shadow: none;
- opacity: .65;
+ opacity: 0.65;
&:hover {
color: $gl-gray-500;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 4268e194ed7..c60bb360a03 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -340,6 +340,8 @@
}
.note-form-actions {
+ color: $gl-text-color;
+
@include media-breakpoint-down(xs) {
.btn {
float: none;
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index bfba1bf1b2b..be535ade0a6 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -618,7 +618,6 @@ ul.notes {
.line-resolve-all-container {
@include notes-media('min', map-get($grid-breakpoints, sm)) {
margin-right: 0;
- padding-left: $gl-padding;
}
> div {
@@ -756,3 +755,23 @@ ul.notes {
margin-top: 4px;
}
}
+
+.discussion-filter-container {
+
+ .btn > svg {
+ width: $gl-col-padding;
+ height: $gl-col-padding;
+ }
+
+ .dropdown-menu {
+ margin-bottom: $gl-padding-4;
+
+ @include media-breakpoint-down(md) {
+ margin-left: $btn-side-margin + $contextual-sidebar-collapsed-width;
+ }
+
+ @include media-breakpoint-down(xs) {
+ margin-left: $btn-side-margin;
+ }
+ }
+}
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 00d2cc01192..6fc336714b6 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -6,11 +6,9 @@ class Admin::ApplicationsController < Admin::ApplicationController
before_action :set_application, only: [:show, :edit, :update, :destroy]
before_action :load_scopes, only: [:new, :create, :edit, :update]
- # rubocop: disable CodeReuse/ActiveRecord
def index
- @applications = Doorkeeper::Application.where("owner_id IS NULL")
+ @applications = ApplicationsFinder.new.execute
end
- # rubocop: enable CodeReuse/ActiveRecord
def show
end
@@ -49,11 +47,9 @@ class Admin::ApplicationsController < Admin::ApplicationController
private
- # rubocop: disable CodeReuse/ActiveRecord
def set_application
- @application = Doorkeeper::Application.where("owner_id IS NULL").find(params[:id])
+ @application = ApplicationsFinder.new(id: params[:id]).execute
end
- # rubocop: enable CodeReuse/ActiveRecord
# Only allow a trusted parameter "white list" through.
def application_params
diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb
index b5fb5511638..23cc9ee247a 100644
--- a/app/controllers/admin/dashboard_controller.rb
+++ b/app/controllers/admin/dashboard_controller.rb
@@ -3,8 +3,8 @@
class Admin::DashboardController < Admin::ApplicationController
include CountHelper
- COUNTED_ITEMS = [Project, User, Group, ForkedProjectLink, Issue, MergeRequest,
- Note, Snippet, Key, Milestone].freeze
+ COUNTED_ITEMS = [Project, User, Group, ForkNetworkMember, ForkNetwork, Issue,
+ MergeRequest, Note, Snippet, Key, Milestone].freeze
# rubocop: disable CodeReuse/ActiveRecord
def index
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 3766b64a091..0d5c8657c9e 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -20,7 +20,7 @@ class AutocompleteController < ApplicationController
end
def user
- user = UserFinder.new(params).execute!
+ user = UserFinder.new(params[:id]).find_by_id!
render json: UserSerializer.new.represent(user)
end
diff --git a/app/controllers/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 4f3d737e3ce..7f874687212 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -18,10 +18,15 @@ module Boards
list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
issues = list_service.execute
issues = issues.page(params[:page]).per(params[:per] || 20).without_count
- make_sure_position_is_set(issues) if Gitlab::Database.read_write?
- issues = issues.preload(:project,
- :milestone,
+ Issue.move_to_end(issues) if Gitlab::Database.read_write?
+ issues = issues.preload(:milestone,
:assignees,
+ project: [
+ :route,
+ {
+ namespace: [:route]
+ }
+ ],
labels: [:priorities],
notes: [:award_emoji, :author]
)
@@ -60,12 +65,6 @@ module Boards
render json: data
end
- def make_sure_position_is_set(issues)
- issues.each do |issue|
- issue.move_to_end && issue.save unless issue.relative_position
- end
- end
-
def issue
@issue ||= issues_finder.find(params[:id])
end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 07e01e903ea..ad9cc0925b7 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -2,6 +2,7 @@
module IssuableActions
extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
included do
before_action :labels, only: [:show, :new, :edit]
@@ -95,10 +96,14 @@ module IssuableActions
def discussions
notes = issuable.discussion_notes
.inc_relations_for_view
+ .with_notes_filter(notes_filter)
.includes(:noteable)
.fresh
- notes = ResourceEvents::MergeIntoNotesService.new(issuable, current_user).execute(notes)
+ if notes_filter != UserPreference::NOTES_FILTERS[:only_comments]
+ notes = ResourceEvents::MergeIntoNotesService.new(issuable, current_user).execute(notes)
+ end
+
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
@@ -110,6 +115,32 @@ module IssuableActions
private
+ def notes_filter
+ strong_memoize(:notes_filter) do
+ notes_filter_param = params[:notes_filter]&.to_i
+
+ # GitLab Geo does not expect database UPDATE or INSERT statements to happen
+ # on GET requests.
+ # This is just a fail-safe in case notes_filter is sent via GET request in GitLab Geo.
+ if Gitlab::Database.read_only?
+ notes_filter_param || current_user&.notes_filter_for(issuable)
+ else
+ notes_filter = current_user&.set_notes_filter(notes_filter_param, issuable) || notes_filter_param
+
+ # We need to invalidate the cache for polling notes otherwise it will
+ # ignore the filter.
+ # The ideal would be to invalidate the cache for each user.
+ issuable.expire_note_etag_cache if notes_filter_updated?
+
+ notes_filter
+ end
+ end
+ end
+
+ def notes_filter_updated?
+ current_user&.user_preference&.previous_changes&.any?
+ end
+
def discussion_serializer
DiscussionSerializer.new(project: project, noteable: issuable, current_user: current_user, note_entity: ProjectNoteEntity)
end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 3a45d6205ab..777b147e2dd 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -17,10 +17,17 @@ module NotesActions
notes_json = { notes: [], last_fetched_at: current_fetched_at }
- notes = notes_finder.execute
- .inc_relations_for_view
+ notes = notes_finder
+ .execute
+ .inc_relations_for_view
+
+ if notes_filter != UserPreference::NOTES_FILTERS[:only_comments]
+ notes =
+ ResourceEvents::MergeIntoNotesService
+ .new(noteable, current_user, last_fetched_at: current_fetched_at)
+ .execute(notes)
+ end
- notes = ResourceEvents::MergeIntoNotesService.new(noteable, current_user, last_fetched_at: current_fetched_at).execute(notes)
notes = prepare_notes_for_rendering(notes)
notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
@@ -224,6 +231,10 @@ module NotesActions
request.headers['X-Last-Fetched-At']
end
+ def notes_filter
+ current_user&.notes_filter_for(params[:target_type])
+ end
+
def notes_finder
@notes_finder ||= NotesFinder.new(project, current_user, finder_params)
end
diff --git a/app/controllers/concerns/sends_blob.rb b/app/controllers/concerns/sends_blob.rb
index 971390d9118..8ecdaced9f5 100644
--- a/app/controllers/concerns/sends_blob.rb
+++ b/app/controllers/concerns/sends_blob.rb
@@ -8,7 +8,7 @@ module SendsBlob
include SendFileUpload
end
- def send_blob(blob, params = {})
+ def send_blob(repository, blob, params = {})
if blob
headers['X-Content-Type-Options'] = 'nosniff'
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index 1dfa814cdd5..e3eec5a020d 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -20,7 +20,7 @@ class Import::GithubController < Import::BaseController
end
def personal_access_token
- session[access_token_key] = params[:personal_access_token]
+ session[access_token_key] = params[:personal_access_token]&.strip
redirect_to status_import_url
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 01801c31327..3c3dc03a4ee 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -38,7 +38,7 @@ class Profiles::KeysController < Profiles::ApplicationController
def get_keys
if params[:username].present?
begin
- user = User.find_by_username(params[:username])
+ user = UserFinder.new(params[:username]).find_by_username
if user.present?
render text: user.all_ssh_keys.join("\n"), content_type: "text/plain"
else
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 1c385c0e15a..1f4a25f82e9 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -8,7 +8,7 @@ class Projects::AvatarsController < Projects::ApplicationController
def show
@blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git)
- send_blob(@blob)
+ send_blob(@repository, @blob)
end
def destroy
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 92d26a13da9..56a884b8a2a 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -9,7 +9,7 @@ class Projects::BlobController < Projects::ApplicationController
include ActionView::Helpers::SanitizeHelper
prepend_before_action :authenticate_user!, only: [:edit]
- before_action :set_request_format, only: [:edit, :show, :update]
+ before_action :set_request_format, only: [:edit, :show, :update, :destroy]
before_action :require_non_empty_project, except: [:new, :create]
before_action :authorize_download_code!
@@ -83,7 +83,7 @@ class Projects::BlobController < Projects::ApplicationController
def destroy
create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
- success_path: -> { project_tree_path(@project, @branch_name) },
+ success_path: -> { after_delete_path },
failure_view: :show,
failure_path: project_blob_path(@project, @id))
end
@@ -191,6 +191,15 @@ class Projects::BlobController < Projects::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
+ def after_delete_path
+ branch = BranchesFinder.new(@repository, search: @ref).execute.first
+ if @repository.tree(branch.target, tree_path).entries.empty?
+ project_tree_path(@project, @ref)
+ else
+ project_tree_path(@project, File.join(@ref, tree_path))
+ end
+ end
+
def editor_variables
@branch_name = params[:branch_name]
@@ -255,9 +264,6 @@ class Projects::BlobController < Projects::ApplicationController
def show_json
set_last_commit_sha
- path_segments = @path.split('/')
- path_segments.pop
- tree_path = path_segments.join('/')
json = {
id: @blob.id,
@@ -283,4 +289,8 @@ class Projects::BlobController < Projects::ApplicationController
render json: json
end
+
+ def tree_path
+ @path.rpartition('/').first
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 8bc3a81d771..757b03d0b0e 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -14,6 +14,9 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues]
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
+ before_action do
+ push_frontend_feature_flag(:ci_environments_status_changes)
+ end
def index
@merge_requests = @issuables
@@ -198,43 +201,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def ci_environments_status
- environments =
- begin
- @merge_request.environments_for(current_user).map do |environment|
- project = environment.project
- deployment = environment.first_deployment_for(@merge_request.diff_head_sha)
-
- stop_url =
- if can?(current_user, :stop_environment, environment)
- stop_project_environment_path(project, environment)
- end
-
- metrics_url =
- if can?(current_user, :read_environment, environment) && environment.has_metrics?
- metrics_project_environment_deployment_path(project, environment, deployment)
- end
-
- metrics_monitoring_url =
- if can?(current_user, :read_environment, environment)
- environment_metrics_path(environment)
- end
-
- {
- id: environment.id,
- name: environment.name,
- url: project_environment_path(project, environment),
- metrics_url: metrics_url,
- metrics_monitoring_url: metrics_monitoring_url,
- stop_url: stop_url,
- external_url: environment.external_url,
- external_url_formatted: environment.formatted_external_url,
- deployed_at: deployment.try(:created_at),
- deployed_at_formatted: deployment.try(:formatted_deployment_time)
- }
- end.compact
- end
+ environments = @merge_request.environments_for(current_user).map do |environment|
+ EnvironmentStatus.new(environment, @merge_request)
+ end
- render json: environments
+ render json: EnvironmentStatusSerializer.new(current_user: current_user).represent(environments)
end
def rebase
diff --git a/app/controllers/projects/mirrors_controller.rb b/app/controllers/projects/mirrors_controller.rb
index 78d5faf2326..53176978416 100644
--- a/app/controllers/projects/mirrors_controller.rb
+++ b/app/controllers/projects/mirrors_controller.rb
@@ -44,6 +44,22 @@ class Projects::MirrorsController < Projects::ApplicationController
redirect_to_repository_settings(project, anchor: 'js-push-remote-settings')
end
+ def ssh_host_keys
+ lookup = SshHostKey.new(project: project, url: params[:ssh_url], compare_host_keys: params[:compare_host_keys])
+
+ if lookup.error.present?
+ # Failed to read keys
+ render json: { message: lookup.error }, status: :bad_request
+ elsif lookup.known_hosts.nil?
+ # Still working, come back later
+ render body: nil, status: :no_content
+ else
+ render json: lookup
+ end
+ rescue ArgumentError => err
+ render json: { message: err.message }, status: :bad_request
+ end
+
private
def remote_mirror
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 4bac763d000..3152a38fd8e 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController
alias_method :awardable, :note
def finder_params
- params.merge(last_fetched_at: last_fetched_at)
+ params.merge(last_fetched_at: last_fetched_at, notes_filter: notes_filter)
end
def authorize_admin_note!
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 1dd5d1ff2e8..42ae5b0ef3c 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -12,6 +12,6 @@ class Projects::RawController < Projects::ApplicationController
def show
@blob = @repository.blob_at(@commit.id, @path)
- send_blob(@blob, inline: (params[:inline] != 'false'))
+ send_blob(@repository, @blob, inline: (params[:inline] != 'false'))
end
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 8c6d87a421f..88dd111132b 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -2,6 +2,7 @@
class Projects::WikisController < Projects::ApplicationController
include PreviewMarkdown
+ include SendsBlob
include Gitlab::Utils::StrongMemoize
before_action :authorize_read_wiki!
@@ -26,16 +27,8 @@ class Projects::WikisController < Projects::ApplicationController
set_encoding_error unless valid_encoding?
render 'show'
- elsif file = @project_wiki.find_file(params[:id], params[:version_id])
- response.headers['Content-Security-Policy'] = "default-src 'none'"
- response.headers['X-Content-Security-Policy'] = "default-src 'none'"
-
- send_data(
- file.raw_data,
- type: file.mime_type,
- disposition: 'inline',
- filename: file.name
- )
+ elsif file_blob
+ send_blob(@project_wiki.repository, file_blob)
elsif can?(current_user, :create_wiki, @project) && view_param == 'create'
@page = build_page(title: params[:id])
@@ -164,4 +157,14 @@ class Projects::WikisController < Projects::ApplicationController
def set_encoding_error
flash.now[:notice] = "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
end
+
+ def file_blob
+ strong_memoize(:file_blob) do
+ commit = @project_wiki.repository.commit(@project_wiki.default_branch)
+
+ next unless commit
+
+ @project_wiki.repository.blob_at(commit.id, params[:id])
+ end
+ end
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 694c3a59e2b..dd9bf17cf0c 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -26,12 +26,9 @@ class SnippetsController < ApplicationController
layout 'snippets'
respond_to :html
- # rubocop: disable CodeReuse/ActiveRecord
def index
if params[:username].present?
- @user = User.find_by(username: params[:username])
-
- return render_404 unless @user
+ @user = UserFinder.new(params[:username]).find_by_username!
@snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope])
.execute.page(params[:page])
@@ -41,7 +38,6 @@ class SnippetsController < ApplicationController
redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path)
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def new
@snippet = PersonalSnippet.new
diff --git a/app/finders/applications_finder.rb b/app/finders/applications_finder.rb
new file mode 100644
index 00000000000..3ded90f3fd5
--- /dev/null
+++ b/app/finders/applications_finder.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class ApplicationsFinder
+ attr_reader :params
+
+ def initialize(params = {})
+ @params = params
+ end
+
+ def execute
+ applications = Doorkeeper::Application.where(owner_id: nil) # rubocop: disable CodeReuse/ActiveRecord
+ by_id(applications)
+ end
+
+ private
+
+ def by_id(applications)
+ return applications unless params[:id]
+
+ Doorkeeper::Application.find_by(id: params[:id]) # rubocop: disable CodeReuse/ActiveRecord
+ end
+end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 1f98ecf95ca..8abfe0c4c17 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -256,7 +256,7 @@ class IssuableFinder
if assignee_id?
User.find_by(id: params[:assignee_id])
elsif assignee_username?
- User.find_by(username: params[:assignee_username])
+ User.find_by_username(params[:assignee_username])
else
nil
end
@@ -284,7 +284,7 @@ class IssuableFinder
if author_id?
User.find_by(id: params[:author_id])
elsif author_username?
- User.find_by(username: params[:author_username])
+ User.find_by_username(params[:author_username])
else
nil
end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index c67c2065440..817aac8b5d5 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -24,6 +24,8 @@ class NotesFinder
def execute
notes = init_collection
notes = since_fetch_at(notes)
+ notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter?
+
notes.fresh
end
@@ -134,4 +136,8 @@ class NotesFinder
last_fetched_at = Time.at(@params.fetch(:last_fetched_at, 0).to_i)
notes.updated_after(last_fetched_at - FETCH_OVERLAP)
end
+
+ def notes_filter?
+ @params[:notes_filter].present?
+ end
end
diff --git a/app/finders/user_finder.rb b/app/finders/user_finder.rb
index 815388c894e..556be4c4338 100644
--- a/app/finders/user_finder.rb
+++ b/app/finders/user_finder.rb
@@ -7,22 +7,52 @@
# times we may want to exclude blocked user. By using this finder (and extending
# it whenever necessary) we can keep this logic in one place.
class UserFinder
- attr_reader :params
+ def initialize(username_or_id)
+ @username_or_id = username_or_id
+ end
+
+ # Tries to find a User by id, returning nil if none could be found.
+ def find_by_id
+ User.find_by_id(@username_or_id)
+ end
- def initialize(params)
- @params = params
+ # Tries to find a User by id, raising a `ActiveRecord::RecordNotFound` if it could
+ # not be found.
+ def find_by_id!
+ User.find(@username_or_id)
end
- # Tries to find a User, returning nil if none could be found.
- # rubocop: disable CodeReuse/ActiveRecord
- def execute
- User.find_by(id: params[:id])
+ # Tries to find a User by username, returning nil if none could be found.
+ def find_by_username
+ User.find_by_username(@username_or_id)
end
- # rubocop: enable CodeReuse/ActiveRecord
- # Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could
+ # Tries to find a User by username, raising a `ActiveRecord::RecordNotFound` if it could
# not be found.
- def execute!
- User.find(params[:id])
+ def find_by_username!
+ User.find_by_username!(@username_or_id)
+ end
+
+ # Tries to find a User by username or id, returning nil if none could be found.
+ def find_by_id_or_username
+ if input_is_id?
+ find_by_id
+ else
+ find_by_username
+ end
+ end
+
+ # Tries to find a User by username or id, raising a `ActiveRecord::RecordNotFound` if it could
+ # not be found.
+ def find_by_id_or_username!
+ if input_is_id?
+ find_by_id!
+ else
+ find_by_username!
+ end
+ end
+
+ def input_is_id?
+ @username_or_id.is_a?(Numeric) || @username_or_id =~ /^\d+$/
end
end
diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index f2ad9b4bda5..81ae50c0bd1 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -43,13 +43,11 @@ class UsersFinder
private
- # rubocop: disable CodeReuse/ActiveRecord
def by_username(users)
return users unless params[:username]
- users.where(username: params[:username])
+ users.by_username(params[:username])
end
- # rubocop: enable CodeReuse/ActiveRecord
def by_search(users)
return users unless params[:search].present?
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 8d58c86b7a4..ff7f1e3a9aa 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -150,7 +150,9 @@ module BlobHelper
# example of Javascript) we tell the browser of the victim not to
# execute untrusted data.
def safe_content_type(blob)
- if blob.text?
+ if blob.extension == 'svg'
+ blob.mime_type
+ elsif blob.text?
'text/plain; charset=utf-8'
elsif blob.image?
blob.content_type
@@ -159,6 +161,12 @@ module BlobHelper
end
end
+ def content_disposition(blob, inline)
+ return 'attachment' if blob.extension == 'svg'
+
+ inline ? 'inline' : 'attachment'
+ end
+
def ref_project
@ref_project ||= @target_project || @project
end
@@ -175,23 +183,23 @@ module BlobHelper
end
private :template_dropdown_names
- def licenses_for_select(project = @project)
+ def licenses_for_select(project)
@licenses_for_select ||= template_dropdown_names(TemplateFinder.build(:licenses, project).execute)
end
- def gitignore_names(project = @project)
+ def gitignore_names(project)
@gitignore_names ||= template_dropdown_names(TemplateFinder.build(:gitignores, project).execute)
end
- def gitlab_ci_ymls(project = @project)
+ def gitlab_ci_ymls(project)
@gitlab_ci_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_ymls, project).execute)
end
- def dockerfile_names(project = @project)
+ def dockerfile_names(project)
@dockerfile_names ||= template_dropdown_names(TemplateFinder.build(:dockerfiles, project).execute)
end
- def blob_editor_paths(project = @project)
+ def blob_editor_paths(project)
{
'relative-url-root' => Rails.application.config.relative_url_root,
'assets-prefix' => Gitlab::Application.config.assets.prefix,
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index 6f9e2ef78cd..923a06a0512 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -42,7 +42,7 @@ module CiStatusHelper
when 'manual'
s_('CiStatusText|blocked')
when 'scheduled'
- s_('CiStatusText|scheduled')
+ s_('CiStatusText|delayed')
else
# All states are already being translated inside the detailed statuses:
# :running => Gitlab::Ci::Status::Running
diff --git a/app/helpers/count_helper.rb b/app/helpers/count_helper.rb
index e16223a82c9..13839474e1f 100644
--- a/app/helpers/count_helper.rb
+++ b/app/helpers/count_helper.rb
@@ -8,4 +8,18 @@ module CountHelper
number_with_delimiter(count)
end
+
+ # This will approximate the fork count by checking all counting all fork network
+ # memberships, and deducting 1 for each root of the fork network.
+ # This might be inacurate as the root of the fork network might have been deleted.
+ #
+ # This makes querying this information a lot more effecient and it should be
+ # accurate enough for the instance wide statistics
+ def approximate_fork_count_with_delimiters(count_data)
+ fork_network_count = count_data[ForkNetwork]
+ fork_network_member_count = count_data[ForkNetworkMember]
+ approximate_fork_count = fork_network_member_count - fork_network_count
+
+ number_with_delimiter(approximate_fork_count)
+ end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index f573fd399a5..0c313e9e6d3 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -1,6 +1,15 @@
# frozen_string_literal: true
module GroupsHelper
+ def group_overview_nav_link_paths
+ %w[
+ groups#show
+ groups#activity
+ groups#subgroups
+ analytics#show
+ ]
+ end
+
def group_nav_link_paths
%w[groups#projects groups#edit badges#index ci_cd#show ldap_group_links#index hooks#index audit_events#index pipeline_quota#index]
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 97406fefd43..6069640b9c8 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -386,8 +386,8 @@ module IssuablesHelper
{
todo_text: "Add todo",
mark_text: "Mark todo as done",
- todo_icon: (is_collapsed ? icon('plus-square') : nil),
- mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil),
+ todo_icon: (is_collapsed ? sprite_icon('todo-add') : nil),
+ mark_icon: (is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : nil),
issuable_id: issuable.id,
issuable_type: issuable.class.name.underscore,
url: project_todos_path(@project),
diff --git a/app/helpers/time_helper.rb b/app/helpers/time_helper.rb
index 3e6a301b77d..719c351242c 100644
--- a/app/helpers/time_helper.rb
+++ b/app/helpers/time_helper.rb
@@ -21,17 +21,15 @@ module TimeHelper
"#{from.to_s(:short)} - #{to.to_s(:short)}"
end
- def duration_in_numbers(duration_in_seconds, allow_overflow = false)
- if allow_overflow
- seconds = duration_in_seconds % 1.minute
- minutes = (duration_in_seconds / 1.minute) % (1.hour / 1.minute)
- hours = duration_in_seconds / 1.hour
+ def duration_in_numbers(duration_in_seconds)
+ seconds = duration_in_seconds % 1.minute
+ minutes = (duration_in_seconds / 1.minute) % (1.hour / 1.minute)
+ hours = duration_in_seconds / 1.hour
- "%02d:%02d:%02d" % [hours, minutes, seconds]
+ if hours == 0
+ "%02d:%02d" % [minutes, seconds]
else
- time_format = duration_in_seconds < 1.hour ? "%M:%S" : "%H:%M:%S"
-
- Time.at(duration_in_seconds).utc.strftime(time_format)
+ "%02d:%02d:%02d" % [hours, minutes, seconds]
end
end
end
diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb
index f19445fca1a..49c08dce96c 100644
--- a/app/helpers/workhorse_helper.rb
+++ b/app/helpers/workhorse_helper.rb
@@ -6,7 +6,7 @@ module WorkhorseHelper
# Send a Git blob through Workhorse
def send_git_blob(repository, blob, inline: true)
headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob))
- headers['Content-Disposition'] = inline ? 'inline' : 'attachment'
+ headers['Content-Disposition'] = content_disposition(blob, inline)
headers['Content-Type'] = safe_content_type(blob)
render plain: ""
end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index cb73fc74bb6..34a889057ab 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -19,7 +19,9 @@ module Ci
sast: 'gl-sast-report.json',
dependency_scanning: 'gl-dependency-scanning-report.json',
container_scanning: 'gl-container-scanning-report.json',
- dast: 'gl-dast-report.json'
+ dast: 'gl-dast-report.json',
+ license_management: 'gl-license-management-report.json',
+ performance: 'performance.json'
}.freeze
TYPE_AND_FORMAT_PAIRS = {
@@ -27,11 +29,17 @@ module Ci
metadata: :gzip,
trace: :raw,
junit: :gzip,
- codequality: :gzip,
- sast: :gzip,
- dependency_scanning: :gzip,
- container_scanning: :gzip,
- dast: :gzip
+
+ # All these file formats use `raw` as we need to store them uncompressed
+ # for Frontend to fetch the files and do analysis
+ # When they will be only used by backend, they can be `gzipped`.
+ codequality: :raw,
+ sast: :raw,
+ dependency_scanning: :raw,
+ container_scanning: :raw,
+ dast: :raw,
+ license_management: :raw,
+ performance: :raw
}.freeze
belongs_to :project
@@ -76,7 +84,9 @@ module Ci
dependency_scanning: 6, ## EE-specific
container_scanning: 7, ## EE-specific
dast: 8, ## EE-specific
- codequality: 9 ## EE-specific
+ codequality: 9, ## EE-specific
+ license_management: 10, ## EE-specific
+ performance: 11 ## EE-specific
}
enum file_format: {
@@ -100,7 +110,8 @@ module Ci
}
FILE_FORMAT_ADAPTERS = {
- gzip: Gitlab::Ci::Build::Artifacts::GzipFileAdapter
+ gzip: Gitlab::Ci::Build::Artifacts::Adapters::GzipStream,
+ raw: Gitlab::Ci::Build::Artifacts::Adapters::RawStream
}.freeze
def valid_file_format?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 17024e8a0af..aeee7f0a5d2 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -268,6 +268,12 @@ module Ci
stage unless stage.statuses_count.zero?
end
+ def ref_exists?
+ project.repository.ref_exists?(git_ref)
+ rescue Gitlab::Git::Repository::NoRepository
+ false
+ end
+
##
# TODO We do not completely switch to persisted stages because of
# race conditions with setting statuses gitlab-ce#23257.
@@ -674,11 +680,11 @@ module Ci
def push_details
strong_memoize(:push_details) do
- Gitlab::Git::Push.new(project, before_sha, sha, push_ref)
+ Gitlab::Git::Push.new(project, before_sha, sha, git_ref)
end
end
- def push_ref
+ def git_ref
if branch?
Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s
elsif tag?
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index a4a2e2b79a6..b311f5e0617 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ActiveRecord::Base
- VERSION = '0.1.31'.freeze
+ VERSION = '0.1.35'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 20d53b8e620..95efecfc41d 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -31,6 +31,9 @@ module Clusters
has_one :application_runner, class_name: 'Clusters::Applications::Runner'
has_one :application_jupyter, class_name: 'Clusters::Applications::Jupyter'
+ has_many :kubernetes_namespaces
+ has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace'
+
accepts_nested_attributes_for :provider_gcp, update_only: true
accepts_nested_attributes_for :platform_kubernetes, update_only: true
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
new file mode 100644
index 00000000000..fb5f6b65d9d
--- /dev/null
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Clusters
+ class KubernetesNamespace < ActiveRecord::Base
+ self.table_name = 'clusters_kubernetes_namespaces'
+
+ belongs_to :cluster_project, class_name: 'Clusters::Project'
+ belongs_to :cluster, class_name: 'Clusters::Cluster'
+ belongs_to :project, class_name: '::Project'
+ has_one :platform_kubernetes, through: :cluster
+
+ validates :namespace, presence: true
+ validates :namespace, uniqueness: { scope: :cluster_id }
+
+ before_validation :set_namespace_and_service_account_to_default, on: :create
+
+ attr_encrypted :service_account_token,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-cbc'
+
+ def token_name
+ "#{namespace}-token"
+ end
+
+ private
+
+ def set_namespace_and_service_account_to_default
+ self.namespace ||= default_namespace
+ self.service_account_name ||= default_service_account_name
+ end
+
+ def default_namespace
+ platform_kubernetes&.namespace.presence || project_namespace
+ end
+
+ def default_service_account_name
+ "#{namespace}-service-account"
+ end
+
+ def project_namespace
+ Gitlab::NamespaceSanitizer.sanitize(project_slug)
+ end
+
+ def project_slug
+ "#{project.path}-#{project.id}".downcase
+ end
+ end
+end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 3a335909101..f0f791742f4 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -7,6 +7,8 @@ module Clusters
include ReactiveCaching
include EnumWithNil
+ RESERVED_NAMESPACES = %w(gitlab-managed-apps).freeze
+
self.table_name = 'cluster_platforms_kubernetes'
self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.id] }
@@ -32,6 +34,8 @@ module Clusters
message: Gitlab::Regex.kubernetes_namespace_regex_message
}
+ validates :namespace, exclusion: { in: RESERVED_NAMESPACES }
+
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
validates :api_url, url: true, presence: true
validates :token, presence: true
@@ -45,6 +49,7 @@ module Clusters
delegate :project, to: :cluster, allow_nil: true
delegate :enabled?, to: :cluster, allow_nil: true
delegate :managed?, to: :cluster, allow_nil: true
+ delegate :kubernetes_namespace, to: :cluster
alias_method :active?, :enabled?
@@ -102,7 +107,7 @@ module Clusters
end
def kubeclient
- @kubeclient ||= build_kube_client!(api_groups: ['api', 'apis/rbac.authorization.k8s.io'])
+ @kubeclient ||= build_kube_client!
end
private
@@ -116,13 +121,22 @@ module Clusters
end
def default_namespace
+ kubernetes_namespace&.namespace.presence || fallback_default_namespace
+ end
+
+ # DEPRECATED
+ #
+ # On 11.4 Clusters::KubernetesNamespace was introduced, this model will allow to
+ # have multiple namespaces per project. This method will be removed after migration
+ # has been completed.
+ def fallback_default_namespace
return unless project
slug = "#{project.path}-#{project.id}".downcase
- slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
+ Gitlab::NamespaceSanitizer.sanitize(slug)
end
- def build_kube_client!(api_groups: ['api'], api_version: 'v1')
+ def build_kube_client!
raise "Incomplete settings" unless api_url && actual_namespace
unless (username && password) || token
@@ -131,8 +145,6 @@ module Clusters
Gitlab::Kubernetes::KubeClient.new(
api_url,
- api_groups,
- api_version,
auth_options: kubeclient_auth_options,
ssl_options: kubeclient_ssl_options,
http_proxy_uri: ENV['http_proxy']
diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb
index 839ce796081..15092b1c9d2 100644
--- a/app/models/clusters/project.rb
+++ b/app/models/clusters/project.rb
@@ -6,5 +6,8 @@ module Clusters
belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :project, class_name: '::Project'
+
+ has_many :kubernetes_namespaces, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
+ has_one :kubernetes_namespace, -> { order(id: :desc) }, class_name: 'Clusters::KubernetesNamespace', foreign_key: :cluster_project_id
end
end
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index 85229cded5d..045bf392ac8 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -12,6 +12,49 @@ module RelativePositioning
after_save :save_positionable_neighbours
end
+ class_methods do
+ def move_to_end(objects)
+ parent_ids = objects.map(&:parent_ids).flatten.uniq
+ max_relative_position = in_parents(parent_ids).maximum(:relative_position) || START_POSITION
+ objects = objects.reject(&:relative_position)
+
+ self.transaction do
+ objects.each do |object|
+ relative_position = position_between(max_relative_position, MAX_POSITION)
+ object.relative_position = relative_position
+ max_relative_position = relative_position
+ object.save
+ end
+ end
+ end
+
+ # This method takes two integer values (positions) and
+ # calculates the position between them. The range is huge as
+ # the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time
+ # when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number
+ def position_between(pos_before, pos_after)
+ pos_before ||= MIN_POSITION
+ pos_after ||= MAX_POSITION
+
+ pos_before, pos_after = [pos_before, pos_after].sort
+
+ halfway = (pos_after + pos_before) / 2
+ distance_to_halfway = pos_after - halfway
+
+ if distance_to_halfway < IDEAL_DISTANCE
+ halfway
+ else
+ if pos_before == MIN_POSITION
+ pos_after - IDEAL_DISTANCE
+ elsif pos_after == MAX_POSITION
+ pos_before + IDEAL_DISTANCE
+ else
+ halfway
+ end
+ end
+ end
+ end
+
def min_relative_position
self.class.in_parents(parent_ids).minimum(:relative_position)
end
@@ -57,7 +100,7 @@ module RelativePositioning
@positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
- self.relative_position = position_between(before.relative_position, after.relative_position)
+ self.relative_position = self.class.position_between(before.relative_position, after.relative_position)
end
def move_after(before = self)
@@ -72,7 +115,7 @@ module RelativePositioning
pos_after = issue_to_move.relative_position
end
- self.relative_position = position_between(pos_before, pos_after)
+ self.relative_position = self.class.position_between(pos_before, pos_after)
end
def move_before(after = self)
@@ -87,15 +130,15 @@ module RelativePositioning
pos_before = issue_to_move.relative_position
end
- self.relative_position = position_between(pos_before, pos_after)
+ self.relative_position = self.class.position_between(pos_before, pos_after)
end
def move_to_end
- self.relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION)
+ self.relative_position = self.class.position_between(max_relative_position || START_POSITION, MAX_POSITION)
end
def move_to_start
- self.relative_position = position_between(min_relative_position || START_POSITION, MIN_POSITION)
+ self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION)
end
# Indicates if there is an issue that should be shifted to free the place
@@ -112,32 +155,6 @@ module RelativePositioning
private
- # This method takes two integer values (positions) and
- # calculates the position between them. The range is huge as
- # the maximum integer value is 2147483647. We are incrementing position by IDEAL_DISTANCE * 2 every time
- # when we have enough space. If distance is less then IDEAL_DISTANCE we are calculating an average number
- def position_between(pos_before, pos_after)
- pos_before ||= MIN_POSITION
- pos_after ||= MAX_POSITION
-
- pos_before, pos_after = [pos_before, pos_after].sort
-
- halfway = (pos_after + pos_before) / 2
- distance_to_halfway = pos_after - halfway
-
- if distance_to_halfway < IDEAL_DISTANCE
- halfway
- else
- if pos_before == MIN_POSITION
- pos_after - IDEAL_DISTANCE
- elsif pos_after == MAX_POSITION
- pos_before + IDEAL_DISTANCE
- else
- halfway
- end
- end
- end
-
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def save_positionable_neighbours
return unless @positionable_neighbours
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
new file mode 100644
index 00000000000..5ff3acc0e58
--- /dev/null
+++ b/app/models/environment_status.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+class EnvironmentStatus
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :environment, :merge_request
+
+ delegate :id, to: :environment
+ delegate :name, to: :environment
+ delegate :project, to: :environment
+ delegate :deployed_at, to: :deployment, allow_nil: true
+
+ def initialize(environment, merge_request)
+ @environment = environment
+ @merge_request = merge_request
+ end
+
+ def deployment
+ strong_memoize(:deployment) do
+ environment.first_deployment_for(merge_request.diff_head_sha)
+ end
+ end
+
+ def deployed_at
+ deployment&.created_at
+ end
+
+ def changes
+ sha = merge_request.diff_head_sha
+ return [] if project.route_map_for(sha).nil?
+
+ changed_files.map { |file| build_change(file, sha) }.compact
+ end
+
+ def changed_files
+ merge_request.merge_request_diff
+ .merge_request_diff_files.where(deleted_file: false)
+ end
+
+ private
+
+ PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
+
+ def build_change(file, sha)
+ public_path = project.public_path_for_source_path(file.new_path, sha)
+ return if public_path.nil?
+
+ ext = File.extname(public_path)
+ return if ext.present? && ext !~ PAGE_EXTENSIONS
+
+ {
+ path: public_path,
+ external_url: environment.external_url_for(file.new_path, sha)
+ }
+ end
+end
diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb
deleted file mode 100644
index 0f7067238cd..00000000000
--- a/app/models/forked_project_link.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-class ForkedProjectLink < ActiveRecord::Base
- belongs_to :forked_to_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
- belongs_to :forked_from_project, -> { where.not(pending_delete: true) }, class_name: 'Project'
-end
diff --git a/app/models/list.rb b/app/models/list.rb
index 1a30acc83cf..029685be927 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -15,6 +15,7 @@ class List < ActiveRecord::Base
scope :destroyable, -> { where(list_type: list_types.slice(*destroyable_types).values) }
scope :movable, -> { where(list_type: list_types.slice(*movable_types).values) }
+ scope :preload_associations, -> { preload(:board, :label) }
class << self
def destroyable_types
diff --git a/app/models/note.rb b/app/models/note.rb
index 95e1d3afa00..e1bd943e8e4 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -110,6 +110,15 @@ class Note < ActiveRecord::Base
:system_note_metadata, :note_diff_file)
end
+ scope :with_notes_filter, -> (notes_filter) do
+ case notes_filter
+ when UserPreference::NOTES_FILTERS[:only_comments]
+ user
+ else
+ all
+ end
+ end
+
scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) }
scope :new_diff_notes, -> { where(type: 'DiffNote') }
scope :non_diff_notes, -> { where(type: ['Note', 'DiscussionNote', nil]) }
diff --git a/app/models/project.rb b/app/models/project.rb
index b80e41e4a96..382fb4f463a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -167,20 +167,15 @@ class Project < ActiveRecord::Base
has_one :packagist_service
has_one :hangouts_chat_service
- # TODO: replace these relations with the fork network versions
- has_one :forked_project_link, foreign_key: "forked_to_project_id"
- has_one :forked_from_project, through: :forked_project_link
-
- has_many :forked_project_links, foreign_key: "forked_from_project_id"
- has_many :forks, through: :forked_project_links, source: :forked_to_project
- # TODO: replace these relations with the fork network versions
-
has_one :root_of_fork_network,
foreign_key: 'root_project_id',
inverse_of: :root_project,
class_name: 'ForkNetwork'
has_one :fork_network_member
has_one :fork_network, through: :fork_network_member
+ has_one :forked_from_project, through: :fork_network_member
+ has_many :forked_to_members, class_name: 'ForkNetworkMember', foreign_key: 'forked_from_project_id'
+ has_many :forks, through: :forked_to_members, source: :project, inverse_of: :forked_from_project
has_one :import_state, autosave: true, class_name: 'ProjectImportState', inverse_of: :project
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -553,6 +548,8 @@ class Project < ActiveRecord::Base
self[:lfs_enabled] && Gitlab.config.lfs.enabled
end
+ alias_method :lfs_enabled, :lfs_enabled?
+
def auto_devops_enabled?
if auto_devops&.enabled.nil?
has_auto_devops_implicitly_enabled?
@@ -693,6 +690,8 @@ class Project < ActiveRecord::Base
else
super
end
+ rescue
+ super
end
def valid_import_url?
@@ -1250,12 +1249,7 @@ class Project < ActiveRecord::Base
end
def forked?
- return true if fork_network && fork_network.root_project != self
-
- # TODO: Use only the above conditional using the `fork_network`
- # This is the old conditional that looks at the `forked_project_link`, we
- # fall back to this while we're migrating the new models
- !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?)
+ fork_network && fork_network.root_project != self
end
def fork_source
@@ -1546,9 +1540,7 @@ class Project < ActiveRecord::Base
def visibility_level_allowed_as_fork?(level = self.visibility_level)
return true unless forked?
- # self.forked_from_project will be nil before the project is saved, so
- # we need to go through the relation
- original_project = forked_project_link&.forked_from_project
+ original_project = fork_source
return true unless original_project
level <= original_project.visibility_level
@@ -1640,34 +1632,6 @@ class Project < ActiveRecord::Base
end
# rubocop: enable CodeReuse/ServiceClass
- def rename_repo
- path_before = previous_changes['path'].first
- full_path_before = full_path_was
- full_path_after = build_full_path
-
- Gitlab::AppLogger.info("Attempting to rename #{full_path_was} -> #{full_path_after}")
-
- if has_container_registry_tags?
- Gitlab::AppLogger.info("Project #{full_path_was} cannot be renamed because container registry tags are present!")
-
- # we currently don't support renaming repository if it contains images in container registry
- raise StandardError.new('Project cannot be renamed, because images are present in its container registry')
- end
-
- expire_caches_before_rename(full_path_before)
-
- if rename_or_migrate_repository!
- Gitlab::AppLogger.info("Project was renamed: #{full_path_before} -> #{full_path_after}")
- after_rename_repository(full_path_before, path_before)
- else
- Gitlab::AppLogger.info("Repository could not be renamed: #{full_path_before} -> #{full_path_after}")
-
- # if we cannot move namespace directory we should rollback
- # db changes in order to prevent out of sync between db and fs
- raise StandardError.new('Repository cannot be renamed')
- end
- end
-
def write_repository_config(gl_full_path: full_path)
# We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using
@@ -2096,51 +2060,6 @@ class Project < ActiveRecord::Base
auto_cancel_pending_pipelines == 'enabled'
end
- private
-
- # rubocop: disable CodeReuse/ServiceClass
- def rename_or_migrate_repository!
- if Gitlab::CurrentSettings.hashed_storage_enabled? &&
- storage_upgradable? &&
- Feature.disabled?(:skip_hashed_storage_upgrade) # kill switch in case we need to disable upgrade behavior
- ::Projects::HashedStorageMigrationService.new(self, full_path_was).execute
- else
- storage.rename_repo
- end
- end
- # rubocop: enable CodeReuse/ServiceClass
-
- def storage_upgradable?
- storage_version != LATEST_STORAGE_VERSION
- end
-
- def after_rename_repository(full_path_before, path_before)
- execute_rename_repository_hooks!(full_path_before)
-
- write_repository_config
-
- # We need to check if project had been rolled out to move resource to hashed storage or not and decide
- # if we need execute any take action or no-op.
- unless hashed_storage?(:attachments)
- Gitlab::UploadsTransfer.new.rename_project(path_before, self.path, namespace.full_path)
- end
-
- Gitlab::PagesTransfer.new.rename_project(path_before, self.path, namespace.full_path)
- end
-
- # rubocop: disable CodeReuse/ServiceClass
- def execute_rename_repository_hooks!(full_path_before)
- # When we import a project overwriting the original project, there
- # is a move operation. In that case we don't want to send the instructions.
- send_move_instructions(full_path_before) unless import_started?
-
- self.old_path_with_namespace = full_path_before
- SystemHooksService.new.execute_hooks_for(self, :rename)
-
- reload_repository!
- end
- # rubocop: enable CodeReuse/ServiceClass
-
def storage
@storage ||=
if hashed_storage?(:repository)
@@ -2150,6 +2069,12 @@ class Project < ActiveRecord::Base
end
end
+ def storage_upgradable?
+ storage_version != LATEST_STORAGE_VERSION
+ end
+
+ private
+
def use_hashed_storage
if self.new_record? && Gitlab::CurrentSettings.hashed_storage_enabled
self.storage_version = LATEST_STORAGE_VERSION
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index d502423726c..d121d088ff6 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -80,13 +80,18 @@ class BambooService < CiService
private
+ def get_build_result_index
+ # When Bamboo returns multiple results for a given changeset, arbitrarily assume the most relevant result to be the last one.
+ -1
+ end
+
def read_build_page(response)
- if response.code != 200 || response['results']['results']['size'] == '0'
+ if response.code != 200 || response.dig('results', 'results', 'size') == '0'
# If actual build link can't be determined, send user to build summary page.
URI.join("#{bamboo_url}/", "browse/#{build_key}").to_s
else
# If actual build link is available, go to build result page.
- result_key = response['results']['results']['result']['planResultKey']['key']
+ result_key = response.dig('results', 'results', 'result', get_build_result_index, 'planResultKey', 'key')
URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s
end
end
@@ -94,10 +99,10 @@ class BambooService < CiService
def read_commit_status(response)
return :error unless response.code == 200 || response.code == 404
- status = if response.code == 404 || response['results']['results']['size'] == '0'
+ status = if response.code == 404 || response.dig('results', 'results', 'size') == '0'
'Pending'
else
- response['results']['results']['result']['buildState']
+ response.dig('results', 'results', 'result', get_build_result_index, 'buildState')
end
if status.include?('Success')
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 2545df06f6b..76624263aab 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -1,53 +1,5 @@
# frozen_string_literal: true
-require "flowdock-git-hook"
-
-# Flow dock depends on Grit to compute the number of commits between two given
-# commits. To make this depend on Gitaly, a monkey patch is applied
-module Flowdock
- class Git
- # pass down a Repository all the way down
- def repo
- @options[:repo]
- end
-
- def config
- {}
- end
-
- def messages
- Git::Builder.new(repo: repo,
- ref: @ref,
- before: @from,
- after: @to,
- commit_url: @commit_url,
- branch_url: @branch_url,
- diff_url: @diff_url,
- repo_url: @repo_url,
- repo_name: @repo_name,
- permanent_refs: @permanent_refs,
- tags: tags
- ).to_hashes
- end
-
- class Builder
- def commits
- @repo.commits_between(@before, @after).map do |commit|
- {
- url: @opts[:commit_url] ? @opts[:commit_url] % [commit.sha] : nil,
- id: commit.sha,
- message: commit.message,
- author: {
- name: commit.author_name,
- email: commit.author_email
- }
- }
- end
- end
- end
- end
-end
-
class FlowdockService < Service
prop_accessor :token
validates :token, presence: true, if: :activated?
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index ba7fcb0cf93..5a38f48c542 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -14,6 +14,9 @@ class JiraService < IssueTrackerService
format: { with: Gitlab::Regex.jira_transition_id_regex, message: "transition ids can have only numbers which can be split with , or ;" },
allow_blank: true
+ # JIRA cloud version is deprecating authentication via username and password.
+ # We should use username/password for JIRA server and email/api_token for JIRA cloud,
+ # for more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/49936.
prop_accessor :username, :password, :url, :api_url, :jira_issue_transition_id, :title, :description
before_update :reset_password
@@ -95,8 +98,8 @@ class JiraService < IssueTrackerService
[
{ type: 'text', name: 'url', title: 'Web URL', placeholder: 'https://jira.example.com', required: true },
{ type: 'text', name: 'api_url', title: 'JIRA API URL', placeholder: 'If different from Web URL' },
- { type: 'text', name: 'username', placeholder: '', required: true },
- { type: 'password', name: 'password', placeholder: '', required: true },
+ { type: 'text', name: 'username', title: 'Username or Email', placeholder: 'Use a username for server version and an email for cloud version', required: true },
+ { type: 'password', name: 'password', title: 'Password or API token', placeholder: 'Use a password for server version and an API token for cloud version', required: true },
{ type: 'text', name: 'jira_issue_transition_id', title: 'Transition ID(s)', placeholder: 'Use , or ; to separate multiple transition IDs' }
]
end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index f119555f16b..798944d0c06 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -144,7 +144,7 @@ class KubernetesService < DeploymentService
end
def kubeclient
- @kubeclient ||= build_kube_client!(api_groups: ['api', 'apis/rbac.authorization.k8s.io'])
+ @kubeclient ||= build_kube_client!
end
def deprecated?
@@ -182,13 +182,11 @@ class KubernetesService < DeploymentService
slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
end
- def build_kube_client!(api_groups: ['api'], api_version: 'v1')
+ def build_kube_client!
raise "Incomplete settings" unless api_url && actual_namespace && token
Gitlab::Kubernetes::KubeClient.new(
api_url,
- api_groups,
- api_version,
auth_options: kubeclient_auth_options,
ssl_options: kubeclient_ssl_options,
http_proxy_uri: ENV['http_proxy']
diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb
index 5b0e5fed092..c34078f13c1 100644
--- a/app/models/project_services/microsoft_teams_service.rb
+++ b/app/models/project_services/microsoft_teams_service.rb
@@ -17,7 +17,7 @@ class MicrosoftTeamsService < ChatNotificationService
'This service sends notifications about projects events to Microsoft Teams channels.<br />
To set up this service:
<ol>
- <li><a href="https://msdn.microsoft.com/en-us/microsoft-teams/connectors">Getting started with 365 Office Connectors For Microsoft Teams</a>.</li>
+ <li><a href="https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/connectors/connectors-using#setting-up-a-custom-incoming-webhook">Setup a custom Incoming Webhook using Office 365 Connectors For Microsoft Teams</a>.</li>
<li>Paste the <strong>Webhook URL</strong> into the field below.</li>
<li>Select events below to enable notifications.</li>
</ol>'
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index f141502d5df..211e5c3fcbf 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -56,7 +56,6 @@ class PrometheusService < MonitoringService
name: 'api_url',
title: 'API URL',
placeholder: s_('PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/'),
- help: s_('PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server.'),
required: true
}
]
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 6ce480c32c4..37a1dd64052 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -487,7 +487,20 @@ class Repository
end
def blob_at(sha, path)
- Blob.decorate(raw_repository.blob_at(sha, path), project)
+ blob = Blob.decorate(raw_repository.blob_at(sha, path), project)
+
+ # Don't attempt to return a special result if there is no blob at all
+ return unless blob
+
+ # Don't attempt to return a special result unless we're looking at HEAD
+ return blob unless head_commit&.sha == sha
+
+ case path
+ when head_tree&.readme_path
+ ReadmeBlob.new(blob, self)
+ else
+ blob
+ end
rescue Gitlab::Git::Repository::NoRepository
nil
end
@@ -569,9 +582,7 @@ class Repository
cache_method :merge_request_template_names, fallback: []
def readme
- if readme = tree(:head)&.readme
- ReadmeBlob.new(readme, self)
- end
+ head_tree&.readme
end
def rendered_readme
diff --git a/app/models/ssh_host_key.rb b/app/models/ssh_host_key.rb
new file mode 100644
index 00000000000..b6844dbe870
--- /dev/null
+++ b/app/models/ssh_host_key.rb
@@ -0,0 +1,130 @@
+# frozen_string_literal: true
+
+# Detected SSH host keys are transiently stored in Redis
+class SshHostKey
+ class Fingerprint < Gitlab::SSHPublicKey
+ attr_reader :index
+
+ def initialize(key, index: nil)
+ super(key)
+
+ @index = index
+ end
+
+ def as_json(*)
+ { bits: bits, fingerprint: fingerprint, type: type, index: index }
+ end
+ end
+
+ include ReactiveCaching
+
+ self.reactive_cache_key = ->(key) { [key.class.to_s, key.id] }
+
+ # Do not refresh the data in the background - it is not expected to change.
+ # This is achieved by making the lifetime shorter than the refresh interval.
+ self.reactive_cache_refresh_interval = 15.minutes
+ self.reactive_cache_lifetime = 10.minutes
+
+ def self.find_by(opts = {})
+ return nil unless opts.key?(:id)
+
+ project_id, url = opts[:id].split(':', 2)
+ project = Project.find_by(id: project_id)
+
+ project.presence && new(project: project, url: url)
+ end
+
+ def self.fingerprint_host_keys(data)
+ return [] unless data.is_a?(String)
+
+ data
+ .each_line
+ .each_with_index
+ .map { |line, index| Fingerprint.new(line, index: index) }
+ .select(&:valid?)
+ end
+
+ attr_reader :project, :url, :compare_host_keys
+
+ def initialize(project:, url:, compare_host_keys: nil)
+ @project = project
+ @url = normalize_url(url)
+ @compare_host_keys = compare_host_keys
+ end
+
+ def id
+ [project.id, url].join(':')
+ end
+
+ def as_json(*)
+ {
+ host_keys_changed: host_keys_changed?,
+ fingerprints: fingerprints,
+ known_hosts: known_hosts
+ }
+ end
+
+ def known_hosts
+ with_reactive_cache { |data| data[:known_hosts] }
+ end
+
+ def fingerprints
+ @fingerprints ||= self.class.fingerprint_host_keys(known_hosts)
+ end
+
+ # Returns true if the known_hosts data differs from the version passed in at
+ # initialization as `compare_host_keys`. Comments, ordering, etc, is ignored
+ def host_keys_changed?
+ cleanup(known_hosts) != cleanup(compare_host_keys)
+ end
+
+ def error
+ with_reactive_cache { |data| data[:error] }
+ end
+
+ def calculate_reactive_cache
+ known_hosts, errors, status =
+ Open3.popen3({}, *%W[ssh-keyscan -T 5 -p #{url.port} -f-]) do |stdin, stdout, stderr, wait_thr|
+ stdin.puts(url.host)
+ stdin.close
+
+ [
+ cleanup(stdout.read),
+ cleanup(stderr.read),
+ wait_thr.value
+ ]
+ end
+
+ # ssh-keyscan returns an exit code 0 in several error conditions, such as an
+ # unknown hostname, so check both STDERR and the exit code
+ if status.success? && !errors.present?
+ { known_hosts: known_hosts }
+ else
+ Rails.logger.debug("Failed to detect SSH host keys for #{id}: #{errors}")
+
+ { error: 'Failed to detect SSH host keys' }
+ end
+ end
+
+ private
+
+ # Remove comments and duplicate entries
+ def cleanup(data)
+ data
+ .to_s
+ .each_line
+ .reject { |line| line.start_with?('#') || line.chomp.empty? }
+ .uniq
+ .sort
+ .join
+ end
+
+ def normalize_url(url)
+ full_url = ::Addressable::URI.parse(url)
+ raise ArgumentError.new("Invalid URL") unless full_url&.scheme == 'ssh'
+
+ Addressable::URI.parse("ssh://#{full_url.host}:#{full_url.inferred_port}")
+ rescue Addressable::URI::InvalidURIError
+ raise ArgumentError.new("Invalid URL")
+ end
+end
diff --git a/app/models/tree.rb b/app/models/tree.rb
index 3641c33254c..cd385872171 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -2,6 +2,7 @@
class Tree
include Gitlab::MarkupHelper
+ include Gitlab::Utils::StrongMemoize
attr_accessor :repository, :sha, :path, :entries
@@ -16,32 +17,36 @@ class Tree
@entries = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive)
end
- def readme
- return @readme if defined?(@readme)
-
- available_readmes = blobs.select do |blob|
- Gitlab::FileDetector.type_of(blob.name) == :readme
- end
-
- previewable_readmes = available_readmes.select do |blob|
- previewable?(blob.name)
- end
-
- plain_readmes = available_readmes.select do |blob|
- plain?(blob.name)
+ def readme_path
+ strong_memoize(:readme_path) do
+ available_readmes = blobs.select do |blob|
+ Gitlab::FileDetector.type_of(blob.name) == :readme
+ end
+
+ previewable_readmes = available_readmes.select do |blob|
+ previewable?(blob.name)
+ end
+
+ plain_readmes = available_readmes.select do |blob|
+ plain?(blob.name)
+ end
+
+ # Prioritize previewable over plain readmes
+ entry = previewable_readmes.first || plain_readmes.first
+ next nil unless entry
+
+ if path == '/'
+ entry.name
+ else
+ File.join(path, entry.name)
+ end
end
+ end
- # Prioritize previewable over plain readmes
- readme_tree = previewable_readmes.first || plain_readmes.first
-
- # Return if we can't preview any of them
- if readme_tree.nil?
- return @readme = nil
+ def readme
+ strong_memoize(:readme) do
+ repository.blob_at(sha, readme_path) if readme_path
end
-
- readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name)
-
- @readme = repository.blob_at(sha, readme_path)
end
def trees
diff --git a/app/models/user.rb b/app/models/user.rb
index a0665518cf5..ca7fc3b058f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -152,6 +152,7 @@ class User < ActiveRecord::Base
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
has_one :status, class_name: 'UserStatus'
+ has_one :user_preference
#
# Validations
@@ -224,6 +225,8 @@ class User < ActiveRecord::Base
enum project_view: [:readme, :activity, :files]
delegate :path, to: :namespace, allow_nil: true, prefix: true
+ delegate :notes_filter_for, to: :user_preference
+ delegate :set_notes_filter, to: :user_preference
state_machine :state, initial: :active do
event :block do
@@ -264,7 +267,7 @@ class User < ActiveRecord::Base
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
- scope :by_username, -> (usernames) { iwhere(username: usernames) }
+ scope :by_username, -> (usernames) { iwhere(username: Array(usernames).map(&:to_s)) }
scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
# Limits the users to those that have TODOs, optionally in the given state.
@@ -1367,6 +1370,11 @@ class User < ActiveRecord::Base
!consented_usage_stats? && 7.days.ago > self.created_at && !has_current_license? && User.single_user?
end
+ # Avoid migrations only building user preference object when needed.
+ def user_preference
+ super.presence || build_user_preference
+ end
+
def todos_limited_to(ids)
todos.where(id: ids)
end
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
new file mode 100644
index 00000000000..6cd91abc261
--- /dev/null
+++ b/app/models/user_preference.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+class UserPreference < ActiveRecord::Base
+ # We could use enums, but Rails 4 doesn't support multiple
+ # enum options with same name for multiple fields, also it creates
+ # extra methods that aren't really needed here.
+ NOTES_FILTERS = { all_notes: 0, only_comments: 1 }.freeze
+
+ belongs_to :user
+
+ validates :issue_notes_filter, :merge_request_notes_filter, inclusion: { in: NOTES_FILTERS.values }, presence: true
+
+ class << self
+ def notes_filters
+ {
+ s_('Notes|Show all activity') => NOTES_FILTERS[:all_notes],
+ s_('Notes|Show comments only') => NOTES_FILTERS[:only_comments]
+ }
+ end
+ end
+
+ def set_notes_filter(filter_id, issuable)
+ # No need to update the column if the value is already set.
+ if filter_id && NOTES_FILTERS.values.include?(filter_id)
+ field = notes_filter_field_for(issuable)
+ self[field] = filter_id
+
+ save if attribute_changed?(field)
+ end
+
+ notes_filter_for(issuable)
+ end
+
+ # Returns the current discussion filter for a given issuable
+ # or issuable type.
+ def notes_filter_for(resource)
+ self[notes_filter_field_for(resource)]
+ end
+
+ private
+
+ def notes_filter_field_for(resource)
+ field_key =
+ if resource.is_a?(Issuable)
+ resource.model_name.param_key
+ else
+ resource
+ end
+
+ "#{field_key}_notes_filter"
+ end
+end
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
index 42fd213d03b..c5e349ae913 100644
--- a/app/models/wiki_page.rb
+++ b/app/models/wiki_page.rb
@@ -160,7 +160,9 @@ class WikiPage
# Returns boolean True or False if this instance
# is an old version of the page.
def historical?
- @page.historical? && last_version.sha != version.sha
+ return false unless last_commit_sha && version
+
+ @page.historical? && last_commit_sha != version.sha
end
# Returns boolean True or False if this instance
diff --git a/app/presenters/ci/build_runner_presenter.rb b/app/presenters/ci/build_runner_presenter.rb
index 880218e2727..300f85e1e9d 100644
--- a/app/presenters/ci/build_runner_presenter.rb
+++ b/app/presenters/ci/build_runner_presenter.rb
@@ -30,12 +30,12 @@ module Ci
def create_reports(reports, expire_in:)
return unless reports&.any?
- reports.map do |k, v|
+ reports.map do |report_type, report_paths|
{
- artifact_type: k.to_sym,
- artifact_format: :gzip,
- name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES[k.to_sym],
- paths: v,
+ artifact_type: report_type.to_sym,
+ artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(report_type.to_sym),
+ name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(report_type.to_sym),
+ paths: report_paths,
when: 'always',
expire_in: expire_in
}
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 066a5b1885c..9ddce0d2c80 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -5,6 +5,7 @@ class BuildDetailsEntity < JobEntity
expose :tag_list, as: :tags
expose :has_trace?, as: :has_trace
expose :stage
+ expose :stuck?, as: :stuck
expose :user, using: UserEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
diff --git a/app/serializers/current_user_entity.rb b/app/serializers/current_user_entity.rb
new file mode 100644
index 00000000000..71d14e727dd
--- /dev/null
+++ b/app/serializers/current_user_entity.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# Always use this entity when rendering data for current user
+# for attributes that does not need to be visible to other users
+# like user preferences.
+class CurrentUserEntity < UserEntity
+ expose :user_preference, using: UserPreferenceEntity
+end
diff --git a/app/serializers/environment_status_entity.rb b/app/serializers/environment_status_entity.rb
new file mode 100644
index 00000000000..3dfa4f204c9
--- /dev/null
+++ b/app/serializers/environment_status_entity.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+class EnvironmentStatusEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id
+ expose :name
+
+ expose :url do |es|
+ project_environment_path(es.project, es.environment)
+ end
+
+ expose :metrics_url, if: ->(*) { can_read_environment? && environment.has_metrics? } do |es|
+ metrics_project_environment_deployment_path(es.project, es.environment, es.deployment)
+ end
+
+ expose :metrics_monitoring_url, if: ->(*) { can_read_environment? } do |es|
+ environment_metrics_path(es.environment)
+ end
+
+ expose :stop_url, if: ->(*) { can_stop_environment? } do |es|
+ stop_project_environment_path(es.project, es.environment)
+ end
+
+ expose :external_url do |es|
+ es.environment.external_url
+ end
+
+ expose :external_url_formatted do |es|
+ es.environment.formatted_external_url
+ end
+
+ expose :deployed_at
+
+ expose :deployed_at_formatted do |es|
+ es.deployment.try(:formatted_deployment_time)
+ end
+
+ expose :changes, if: ->(*) { Feature.enabled?(:ci_environments_status_changes, project) }
+
+ private
+
+ def environment
+ object.environment
+ end
+
+ def project
+ object.environment.project
+ end
+
+ def current_user
+ request.current_user
+ end
+
+ def can_read_environment?
+ can?(current_user, :read_environment, environment)
+ end
+
+ def can_stop_environment?
+ can?(current_user, :stop_environment, environment)
+ end
+end
diff --git a/app/serializers/environment_status_serializer.rb b/app/serializers/environment_status_serializer.rb
new file mode 100644
index 00000000000..f8d37934763
--- /dev/null
+++ b/app/serializers/environment_status_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class EnvironmentStatusSerializer < BaseSerializer
+ entity EnvironmentStatusEntity
+end
diff --git a/app/serializers/merge_request_user_entity.rb b/app/serializers/merge_request_user_entity.rb
index fd2d2897113..53257b0602c 100644
--- a/app/serializers/merge_request_user_entity.rb
+++ b/app/serializers/merge_request_user_entity.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class MergeRequestUserEntity < UserEntity
+class MergeRequestUserEntity < CurrentUserEntity
include RequestAwareEntity
include BlobHelper
include TreeHelper
diff --git a/app/serializers/user_preference_entity.rb b/app/serializers/user_preference_entity.rb
new file mode 100644
index 00000000000..fbdaab459b3
--- /dev/null
+++ b/app/serializers/user_preference_entity.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class UserPreferenceEntity < Grape::Entity
+ expose :issue_notes_filter
+ expose :merge_request_notes_filter
+
+ expose :notes_filters do |user_preference|
+ UserPreference.notes_filters
+ end
+end
diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb
index 4c5e22bdd7e..201048aaba5 100644
--- a/app/services/audit_event_service.rb
+++ b/app/services/audit_event_service.rb
@@ -17,11 +17,29 @@ class AuditEventService
end
def security_event
- SecurityEvent.create(
+ log_security_event_to_file
+ log_security_event_to_database
+ end
+
+ private
+
+ def base_payload
+ {
author_id: @author.id,
entity_id: @entity.id,
- entity_type: @entity.class.name,
- details: @details
- )
+ entity_type: @entity.class.name
+ }
+ end
+
+ def file_logger
+ @file_logger ||= Gitlab::AuditJsonLogger.build
+ end
+
+ def log_security_event_to_file
+ file_logger.info(base_payload.merge(@details))
+ end
+
+ def log_security_event_to_database
+ SecurityEvent.create(base_payload.merge(details: @details))
end
end
diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb
index e10eb52e041..5cf5f14a55b 100644
--- a/app/services/boards/lists/list_service.rb
+++ b/app/services/boards/lists/list_service.rb
@@ -6,7 +6,7 @@ module Boards
def execute(board)
board.lists.create(list_type: :backlog) unless board.lists.backlog.exists?
- board.lists
+ board.lists.preload_associations
end
end
end
diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb
index 3ae0a4a19d0..6ee63db8eb9 100644
--- a/app/services/clusters/gcp/finalize_creation_service.rb
+++ b/app/services/clusters/gcp/finalize_creation_service.rb
@@ -60,18 +60,15 @@ module Clusters
'https://' + gke_cluster.endpoint,
Base64.decode64(gke_cluster.master_auth.cluster_ca_certificate),
gke_cluster.master_auth.username,
- gke_cluster.master_auth.password,
- api_groups: ['api', 'apis/rbac.authorization.k8s.io']
+ gke_cluster.master_auth.password
)
end
- def build_kube_client!(api_url, ca_pem, username, password, api_groups: ['api'], api_version: 'v1')
+ def build_kube_client!(api_url, ca_pem, username, password)
raise "Incomplete settings" unless api_url && username && password
Gitlab::Kubernetes::KubeClient.new(
api_url,
- api_groups,
- api_version,
auth_options: { username: username, password: password },
ssl_options: kubeclient_ssl_options(ca_pem),
http_proxy_uri: ENV['http_proxy']
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index fe47aa2f140..0bf0e967dcc 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -14,9 +14,11 @@ module Groups
group.assign_attributes(params)
begin
- after_update if group.save
+ success = group.save
- true
+ after_update if success
+
+ success
rescue Gitlab::UpdatePathError => e
group.errors.add(:base, e.message)
diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb
new file mode 100644
index 00000000000..4131da44f5a
--- /dev/null
+++ b/app/services/projects/after_rename_service.rb
@@ -0,0 +1,135 @@
+# frozen_string_literal: true
+
+module Projects
+ # Service class for performing operations that should take place after a
+ # project has been renamed.
+ #
+ # Example usage:
+ #
+ # project = Project.find(42)
+ #
+ # project.update(...)
+ #
+ # Projects::AfterRenameService.new(project).execute
+ class AfterRenameService
+ attr_reader :project, :full_path_before, :full_path_after, :path_before
+
+ RenameFailedError = Class.new(StandardError)
+
+ # @param [Project] project The Project of the repository to rename.
+ def initialize(project)
+ @project = project
+
+ # The full path of the namespace + project, before the rename took place.
+ @full_path_before = project.full_path_was
+
+ # The full path of the namespace + project, after the rename took place.
+ @full_path_after = project.build_full_path
+
+ # The path of just the project, before the rename took place.
+ @path_before = project.path_was
+ end
+
+ def execute
+ first_ensure_no_registry_tags_are_present
+ expire_caches_before_rename
+ rename_or_migrate_repository!
+ send_move_instructions
+ execute_system_hooks
+ update_repository_configuration
+ rename_transferred_documents
+ log_completion
+ end
+
+ def first_ensure_no_registry_tags_are_present
+ return unless project.has_container_registry_tags?
+
+ raise RenameFailedError.new(
+ "Project #{full_path_before} cannot be renamed because images are " \
+ "present in its container registry"
+ )
+ end
+
+ def expire_caches_before_rename
+ project.expire_caches_before_rename(full_path_before)
+ end
+
+ def rename_or_migrate_repository!
+ success =
+ if migrate_to_hashed_storage?
+ ::Projects::HashedStorageMigrationService
+ .new(project, full_path_before)
+ .execute
+ else
+ project.storage.rename_repo
+ end
+
+ rename_failed! unless success
+ end
+
+ def send_move_instructions
+ return unless send_move_instructions?
+
+ project.send_move_instructions(full_path_before)
+ end
+
+ def execute_system_hooks
+ project.old_path_with_namespace = full_path_before
+ SystemHooksService.new.execute_hooks_for(project, :rename)
+ end
+
+ def update_repository_configuration
+ project.reload_repository!
+ project.write_repository_config
+ end
+
+ def rename_transferred_documents
+ if rename_uploads?
+ Gitlab::UploadsTransfer
+ .new
+ .rename_project(path_before, project_path, namespace_full_path)
+ end
+
+ Gitlab::PagesTransfer
+ .new
+ .rename_project(path_before, project_path, namespace_full_path)
+ end
+
+ def log_completion
+ Gitlab::AppLogger.info(
+ "Project #{project.id} has been renamed from " \
+ "#{full_path_before} to #{full_path_after}"
+ )
+ end
+
+ def migrate_to_hashed_storage?
+ Gitlab::CurrentSettings.hashed_storage_enabled? &&
+ project.storage_upgradable? &&
+ Feature.disabled?(:skip_hashed_storage_upgrade)
+ end
+
+ def send_move_instructions?
+ !project.import_started?
+ end
+
+ def rename_uploads?
+ !project.hashed_storage?(:attachments)
+ end
+
+ def project_path
+ project.path
+ end
+
+ def namespace_full_path
+ project.namespace.full_path
+ end
+
+ def rename_failed!
+ error = "Repository #{full_path_before} could not be renamed to #{full_path_after}"
+
+ Gitlab::AppLogger.error(error)
+
+ raise RenameFailedError.new(error)
+ end
+ end
+end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 0e6a7e8da54..20bfe5af7a1 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -13,8 +13,8 @@ module Projects
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
- forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data)
+ relations_block = params.delete(:relations_block)
@project = Project.new(params)
@@ -24,11 +24,6 @@ module Projects
return @project
end
- unless allowed_fork?(forked_from_project_id)
- @project.errors.add(:forked_from_project_id, 'is forbidden')
- return @project
- end
-
set_project_name_from_path
# get namespace id
@@ -47,6 +42,7 @@ module Projects
@project.namespace_id = current_user.namespace_id
end
+ relations_block&.call(@project)
yield(@project) if block_given?
# If the block added errors, don't try to save the project
@@ -54,10 +50,6 @@ module Projects
@project.creator = current_user
- if forked_from_project_id
- @project.build_forked_project_link(forked_from_project_id: forked_from_project_id)
- end
-
save_project_and_import_data(import_data)
after_create_actions if @project.persisted?
@@ -80,15 +72,6 @@ module Projects
end
# rubocop: disable CodeReuse/ActiveRecord
- def allowed_fork?(source_project_id)
- return true if source_project_id.nil?
-
- source_project = Project.find_by(id: source_project_id)
- current_user.can?(:fork_project, source_project)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- # rubocop: disable CodeReuse/ActiveRecord
def allowed_namespace?(user, namespace_id)
namespace = Namespace.find_by(id: namespace_id)
current_user.can?(:create_projects, namespace)
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index cbbb88a9410..8dc0e044875 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -12,31 +12,42 @@ module Projects
private
+ def allowed_fork?
+ current_user.can?(:fork_project, @project)
+ end
+
def link_existing_project(fork_to_project)
return if fork_to_project.forked?
- link_fork_network(fork_to_project)
+ build_fork_network_member(fork_to_project)
- # A forked project stores its LFS objects in the `forked_from_project`.
- # So the LFS objects become inaccessible, and therefore delete them from
- # the database so they'll get cleaned up.
- #
- # TODO: refactor this to get the correct lfs objects when implementing
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
- fork_to_project.lfs_objects_projects.delete_all
+ if link_fork_network(fork_to_project)
+ # A forked project stores its LFS objects in the `forked_from_project`.
+ # So the LFS objects become inaccessible, and therefore delete them from
+ # the database so they'll get cleaned up.
+ #
+ # TODO: refactor this to get the correct lfs objects when implementing
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/39769
+ fork_to_project.lfs_objects_projects.delete_all
- fork_to_project
+ fork_to_project
+ end
end
def fork_new_project
new_params = {
- forked_from_project_id: @project.id,
visibility_level: allowed_visibility_level,
description: @project.description,
name: @project.name,
path: @project.path,
shared_runners_enabled: @project.shared_runners_enabled,
- namespace_id: target_namespace.id
+ namespace_id: target_namespace.id,
+ fork_network: fork_network,
+ # We need to assign the fork network membership after the project has
+ # been instantiated to avoid ActiveRecord trying to create it when
+ # initializing the project, as that would cause a foreign key constraint
+ # exception.
+ relations_block: -> (project) { build_fork_network_member(project) }
}
if @project.avatar.present? && @project.avatar.image?
@@ -46,38 +57,35 @@ module Projects
new_project = CreateService.new(current_user, new_params).execute
return new_project unless new_project.persisted?
+ # Set the forked_from_project relation after saving to avoid having to
+ # reload the project to reset the association information and cause an
+ # extra query.
+ new_project.forked_from_project = @project
+
builds_access_level = @project.project_feature.builds_access_level
new_project.project_feature.update(builds_access_level: builds_access_level)
- link_fork_network(new_project)
-
new_project
end
def fork_network
- if @project.fork_network
- @project.fork_network
- elsif forked_from_project = @project.forked_from_project
- # TODO: remove this case when all background migrations have completed
- # this only happens when a project had a `forked_project_link` that was
- # not migrated to the `fork_network` relation
- forked_from_project.fork_network || forked_from_project.create_root_of_fork_network
+ @fork_network ||= @project.fork_network || @project.build_root_of_fork_network
+ end
+
+ def build_fork_network_member(fork_to_project)
+ if allowed_fork?
+ fork_to_project.build_fork_network_member(forked_from_project: @project,
+ fork_network: fork_network)
else
- @project.create_root_of_fork_network
+ fork_to_project.errors.add(:forked_from_project_id, 'is forbidden')
end
end
def link_fork_network(fork_to_project)
- fork_network.fork_network_members.create(project: fork_to_project,
- forked_from_project: @project)
-
- # TODO: remove this when ForkedProjectLink model is removed
- unless fork_to_project.forked_project_link
- fork_to_project.create_forked_project_link(forked_to_project: fork_to_project,
- forked_from_project: @project)
- end
+ return if fork_to_project.errors.any?
- refresh_forks_count
+ fork_to_project.fork_network_member.save &&
+ refresh_forks_count
end
def refresh_forks_count
diff --git a/app/services/projects/forks_count_service.rb b/app/services/projects/forks_count_service.rb
index 00e73148358..ca85e2dc281 100644
--- a/app/services/projects/forks_count_service.rb
+++ b/app/services/projects/forks_count_service.rb
@@ -9,10 +9,7 @@ module Projects
# rubocop: disable CodeReuse/ActiveRecord
def self.query(project_ids)
- # We can't directly change ForkedProjectLink to ForkNetworkMember here
- # Nowadays, when a call using v3 to projects/:id/fork is made,
- # the relationship to ForkNetworkMember is not updated
- ForkedProjectLink.where(forked_from_project: project_ids)
+ ForkNetworkMember.where(forked_from_project: project_ids)
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/services/projects/move_forks_service.rb b/app/services/projects/move_forks_service.rb
index 2948555a17c..33f0bab12c9 100644
--- a/app/services/projects/move_forks_service.rb
+++ b/app/services/projects/move_forks_service.rb
@@ -6,7 +6,6 @@ module Projects
return unless super && source_project.fork_network
Project.transaction(requires_new: true) do
- move_forked_project_links
move_fork_network_members
update_root_project
refresh_forks_count
@@ -18,18 +17,6 @@ module Projects
private
# rubocop: disable CodeReuse/ActiveRecord
- def move_forked_project_links
- # Update ancestor
- ForkedProjectLink.where(forked_to_project: source_project)
- .update_all(forked_to_project_id: @project.id)
-
- # Update the descendants
- ForkedProjectLink.where(forked_from_project: source_project)
- .update_all(forked_from_project_id: @project.id)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- # rubocop: disable CodeReuse/ActiveRecord
def move_fork_network_members
ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id)
ForkNetworkMember.where(forked_from_project: source_project).update_all(forked_from_project_id: @project.id)
diff --git a/app/services/projects/unlink_fork_service.rb b/app/services/projects/unlink_fork_service.rb
index a8b7c7f136a..1b8a920268f 100644
--- a/app/services/projects/unlink_fork_service.rb
+++ b/app/services/projects/unlink_fork_service.rb
@@ -25,7 +25,6 @@ module Projects
end
@project.fork_network_member.destroy
- @project.forked_project_link.destroy
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index f25a4e30938..93e48fc0199 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -67,7 +67,7 @@ module Projects
end
if project.previous_changes.include?('path')
- project.rename_repo
+ AfterRenameService.new(project).execute
else
system_hook_service.execute_hooks_for(project, :update)
end
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index defa579f9a8..751aae2696d 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -643,7 +643,7 @@ module QuickActions
if users.empty?
users =
- if params == 'me'
+ if params.strip == 'me'
[current_user]
else
User.where(username: params.split(' ').map(&:strip))
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 34724e0250d..1fee8bfcd31 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -22,7 +22,7 @@ class WebHookService
end
def execute
- start_time = Time.now
+ start_time = Gitlab::Metrics::System.monotonic_time
response = if parsed_url.userinfo.blank?
make_request(hook.url)
@@ -35,7 +35,7 @@ class WebHookService
url: hook.url,
request_data: data,
response: response,
- execution_duration: Time.now - start_time
+ execution_duration: Gitlab::Metrics::System.monotonic_time - start_time
)
{
@@ -43,13 +43,13 @@ class WebHookService
http_status: response.code,
message: response.to_s
}
- rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError => e
+ rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Net::OpenTimeout, Net::ReadTimeout, Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep => e
log_execution(
trigger: hook_name,
url: hook.url,
request_data: data,
response: InternalErrorResponse.new,
- execution_duration: Time.now - start_time,
+ execution_duration: Gitlab::Metrics::System.monotonic_time - start_time,
error_message: e.to_s
)
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 85c04f8a01d..7ac79cc77f5 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -42,7 +42,7 @@
%p
Forks
%span.light.float-right
- = approximate_count_with_delimiters(@counts, ForkedProjectLink)
+ = approximate_fork_count_with_delimiters(@counts)
%p
Issues
%span.light.float-right
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 0c683f86252..5f205d1bcbc 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -63,10 +63,9 @@
.card
.card-header
- %h3.card-title
- = _('Projects')
- %span.badge.badge-pill
- #{@group.projects.count}
+ = _('Projects')
+ %span.badge.badge-pill
+ #{@group.projects.count}
%ul.content-list
- @projects.each do |project|
%li
@@ -119,7 +118,7 @@
= _("<strong>%{group_name}</strong> group members").html_safe % { group_name: @group.name }
%span.badge.badge-pill= @group.members.size
.float-right
- = link_to icon('pencil-square-o', text: _('Manage access')), polymorphic_url([@group, :members]), class: "btn btn-sm"
+ = link_to icon('pencil-square-o', text: _('Manage access')), group_group_members_path(@group), class: "btn btn-sm"
%ul.content-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.card-footer
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index fefb4c7455d..03cce4745aa 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -183,7 +183,7 @@
project members
%span.badge.badge-pill= @project.users.size
.float-right
- = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-sm"
+ = link_to icon('pencil-square-o', text: 'Manage access'), project_project_members_path(@project), class: "btn btn-sm"
%ul.content-list.project_members.members-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.card-footer
diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml
index 7503548fa3d..ec1a3fef435 100644
--- a/app/views/dashboard/_activity_head.html.haml
+++ b/app/views/dashboard/_activity_head.html.haml
@@ -1,3 +1,6 @@
+.page-title-holder
+ %h1.page-title= _('Activity')
+
.top-area
%ul.nav-links.nav.nav-tabs
%li{ class: active_when(params[:filter].nil?) }>
diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml
index 727784141bb..8ab5dc37f34 100644
--- a/app/views/dashboard/_groups_head.html.haml
+++ b/app/views/dashboard/_groups_head.html.haml
@@ -1,3 +1,10 @@
+.page-title-holder
+ %h1.page-title= _('Groups')
+
+ - if current_user.can_create_group?
+ .page-title-controls
+ = link_to _("New group"), new_group_path, class: "btn btn-success"
+
.top-area
%ul.nav-links.mobile-separator.nav.nav-tabs
= nav_link(page: dashboard_groups_path) do
@@ -9,5 +16,3 @@
.nav-controls
= render 'shared/groups/search_form'
= render 'shared/groups/dropdown'
- - if current_user.can_create_group?
- = link_to _("New group"), new_group_path, class: "btn btn-success"
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 69a2e408073..1050945b15a 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -1,6 +1,13 @@
= content_for :flash_message do
= render 'shared/project_limit'
+.page-title-holder
+ %h1.page-title= _('Projects')
+
+ - if current_user.can_create_project?
+ .page-title-controls
+ = link_to "New project", new_project_path, class: "btn btn-success"
+
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
@@ -18,5 +25,3 @@
.nav-controls
= render 'shared/projects/search_form'
= render 'shared/projects/dropdown'
- - if current_user.can_create_project?
- = link_to "New project", new_project_path, class: "btn btn-success"
diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml
index 4f38339b87a..8d99f84755a 100644
--- a/app/views/dashboard/_snippets_head.html.haml
+++ b/app/views/dashboard/_snippets_head.html.haml
@@ -1,3 +1,10 @@
+.page-title-holder
+ %h1.page-title= _('Snippets')
+
+ - if current_user
+ .page-title-controls
+ = link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet"
+
.top-area
%ul.nav-links.nav.nav-tabs
= nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
@@ -6,7 +13,3 @@
= nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
Explore snippets
-
- - if current_user
- .nav-controls.d-none.d-sm-block
- = link_to "New snippet", new_snippet_path, class: "btn btn-success", title: "New snippet"
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 86a21e24ac9..832ba877558 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,11 +4,17 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
+.page-title-holder
+ %h1.page-title= _('Issues')
+
+ - if current_user
+ .page-title-controls
+ = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
+
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
= render 'shared/issuable/feed_buttons'
- = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
= render 'shared/issuable/filter', type: :issues
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 61aae31be60..fba8d1cf667 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -2,10 +2,15 @@
- page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id)
+.page-title-holder
+ %h1.page-title= _('Merge Requests')
+
+ - if current_user
+ .page-title-controls
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
+
.top-area
= render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set
- .nav-controls
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index f66e2b40d76..ae0e38bf0ee 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -2,12 +2,18 @@
- page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path
+.page-title-holder
+ %h1.page-title= _('Milestones')
+
+ - if current_user
+ .page-title-controls
+ = render 'shared/new_project_item_select',
+ path: 'milestones/new', label: 'New milestone',
+ include_groups: true, type: :milestones
+
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
- .nav-controls
- = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
-
.milestones
%ul.content-list
- if @milestones.blank?
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 8b3974d97f8..d2593179f17 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -2,6 +2,9 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
+.page-title-holder
+ %h1.page-title= _('Todos')
+
- if current_user.todos.any?
.top-area
%ul.nav-links.mobile-separator.nav.nav-tabs
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index f3792c5e397..869c54d89ea 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -37,6 +37,8 @@
.settings-content
= render 'shared/badges/badge_settings'
+= render_if_exists 'groups/templates_setting', expanded: expanded
+
%section.settings.gs-advanced.no-animate#js-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 86c5f6a7aa3..684b51b8552 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -1,5 +1,4 @@
-- @breadcrumb_link = dashboard_groups_path
-- breadcrumb_title "Groups"
+- @hide_breadcrumbs = true
- @hide_top_links = true
- page_title 'New Group'
- header_title "Groups", dashboard_groups_path
diff --git a/app/views/instance_statistics/conversational_development_index/_disabled.html.haml b/app/views/instance_statistics/conversational_development_index/_disabled.html.haml
index 0a5717f75e1..b854e15d36f 100644
--- a/app/views/instance_statistics/conversational_development_index/_disabled.html.haml
+++ b/app/views/instance_statistics/conversational_development_index/_disabled.html.haml
@@ -11,4 +11,4 @@
%p
= _('Enable usage ping to get an overview of how you are using GitLab from a feature perspective.')
- if current_user.admin?
- = link_to _('Enable usage ping'), admin_application_settings_path(anchor: 'usage-statistics'), class: 'btn btn-primary'
+ = link_to _('Enable usage ping'), metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), class: 'btn btn-primary'
diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml
index 8bd5708d490..2cdaa85bdaa 100644
--- a/app/views/layouts/_flash.html.haml
+++ b/app/views/layouts/_flash.html.haml
@@ -6,5 +6,5 @@
-# Don't show a flash message if the message is nil
- if value
%div{ class: "flash-#{key}" }
- %div{ class: "#{container_class} #{extra_flash_class}" }
+ %div{ class: "#{(container_class unless fluid_layout)} #{(extra_flash_class unless @no_container)} #{@content_class}" }
%span= value
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index a41d30da450..1b2a4cd6780 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -6,12 +6,13 @@
.mobile-overlay
.alert-wrapper
= render "layouts/broadcast"
- = render 'layouts/header/read_only_banner'
+ = render "layouts/header/read_only_banner"
= yield :flash_message
= render "shared/ping_consent"
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
- = render "layouts/flash"
+ = render "layouts/flash", extra_flash_class: 'limit-container-width'
+ .d-flex
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" }
= yield
diff --git a/app/views/layouts/dashboard.html.haml b/app/views/layouts/dashboard.html.haml
index 489ef245a4d..c10be282952 100644
--- a/app/views/layouts/dashboard.html.haml
+++ b/app/views/layouts/dashboard.html.haml
@@ -1,5 +1,6 @@
- page_title _("Dashboard")
- header_title _("Dashboard"), root_path unless header_title
- sidebar "dashboard"
+- @hide_breadcrumbs = true
= render template: "layouts/application"
diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml
index 80bda34a3f5..24751ab4e06 100644
--- a/app/views/layouts/explore.html.haml
+++ b/app/views/layouts/explore.html.haml
@@ -1,4 +1,6 @@
- page_title _("Explore")
+- @hide_breadcrumbs = true
+
- unless current_user
- header_title _("Explore GitLab"), explore_root_path
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 261d758622b..4f3e4031fe3 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -6,9 +6,11 @@
= current_user.name
= current_user.to_reference
- if current_user.status
- .user-status-emoji.str-truncated.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
- = emoji_icon current_user.status.emoji
- = current_user.status.message_html.html_safe
+ .user-status.d-flex.align-items-center.prepend-top-2.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } }
+ %span.user-status-emoji.d-flex.align-items-center
+ = emoji_icon current_user.status.emoji
+ %span.user-status-message.str-truncated
+ = current_user.status.message_html.html_safe
%li.divider
- if can?(current_user, :update_user_status, current_user)
%li
@@ -19,12 +21,7 @@
- if current_user_menu?(:settings)
%li
= link_to s_("CurrentUser|Settings"), profile_path
- - if current_user_menu?(:help)
- %li
- = link_to _("Help"), help_path
- - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
- %li.divider
- = render 'shared/user_dropdown_contributing_link'
- if current_user_menu?(:sign_out)
+ %li.divider
%li
= link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 39604611440..596fc3985b3 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -53,6 +53,12 @@
= sprite_icon('todo-done', size: 16)
%span.badge.badge-pill.todos-count{ class: ('hidden' if todos_pending_count.zero?) }
= todos_count_format(todos_pending_count)
+ %li.nav-item.header-help.dropdown
+ = link_to help_path, class: 'header-help-dropdown-toggle', data: { toggle: "dropdown" } do
+ = sprite_icon('question', size: 16)
+ = sprite_icon('angle-down', css_class: 'caret-down')
+ .dropdown-menu.dropdown-menu-right
+ = render 'layouts/header/help_dropdown'
- if header_link?(:user_dropdown)
%li.nav-item.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
new file mode 100644
index 00000000000..953c0e7f46c
--- /dev/null
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -0,0 +1,6 @@
+%ul
+ - if current_user_menu?(:help)
+ %li
+ = link_to _("Help"), help_path
+ - if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
+ = render 'shared/user_dropdown_contributing_link'
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 4aa22138498..163556f4509 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -12,7 +12,7 @@
= @group.name
%ul.sidebar-top-level-items.qa-group-sidebar
- if group_sidebar_link?(:overview)
- = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups', 'analytics#show'], html_options: { class: 'home' }) do
+ = nav_link(path: group_overview_nav_link_paths, html_options: { class: 'home' }) do
= link_to group_path(@group) do
.nav-icon-container
= sprite_icon('home')
@@ -36,6 +36,16 @@
%span
= _('Activity')
+ = render_if_exists 'groups/sidebar/security_dashboard'
+
+ - if group_sidebar_link?(:contribution_analytics)
+ = nav_link(path: 'analytics#show') do
+ = link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
+ %span
+ Contribution Analytics
+
+ = render_if_exists "layouts/nav/ee/epic_link", group: @group
+
- if group_sidebar_link?(:issues)
= nav_link(path: issues_sub_menu_items) do
= link_to issues_group_path(@group) do
@@ -132,4 +142,6 @@
%span
= _('CI / CD')
+ = render_if_exists "groups/ee/settings_nav"
+
= render 'shared/sidebar_toggle_button'
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 3625224fbcd..174033f3d49 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -86,7 +86,7 @@
- if project_nav_tab? :issues
= nav_link(controller: @project.issues_enabled? ? [:issues, :labels, :milestones, :boards] : :issues) do
- = link_to project_issues_path(@project), class: 'shortcuts-issues' do
+ = link_to project_issues_path(@project), class: 'shortcuts-issues qa-issues-item' do
.nav-icon-container
= sprite_icon('issues')
%span.nav-item-name
@@ -115,7 +115,7 @@
= boards_link_text
= nav_link(controller: :labels) do
- = link_to project_labels_path(@project), title: _('Labels') do
+ = link_to project_labels_path(@project), title: _('Labels'), class: 'qa-labels-link' do
%span
= _('Labels')
diff --git a/app/views/layouts/terms.html.haml b/app/views/layouts/terms.html.haml
index 977eb350365..cdad617f006 100644
--- a/app/views/layouts/terms.html.haml
+++ b/app/views/layouts/terms.html.haml
@@ -16,19 +16,18 @@
.content{ id: "content-body" }
.card
.card-header
- .card-title
- = brand_header_logo
- - logo_text = brand_header_logo_type
- - if logo_text.present?
- %span.logo-text.prepend-left-8
- = logo_text
- - if header_link?(:user_dropdown)
- .navbar-collapse
- %ul.nav.navbar-nav
- %li.header-user.dropdown
- = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
- = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
- = sprite_icon('angle-down', css_class: 'caret-down')
- .dropdown-menu.dropdown-menu-right
- = render 'layouts/header/current_user_dropdown'
+ = brand_header_logo
+ - logo_text = brand_header_logo_type
+ - if logo_text.present?
+ %span.logo-text.prepend-left-8
+ = logo_text
+ - if header_link?(:user_dropdown)
+ .navbar-collapse
+ %ul.nav.navbar-nav
+ %li.header-user.dropdown
+ = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
+ = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
+ = sprite_icon('angle-down', css_class: 'caret-down')
+ .dropdown-menu.dropdown-menu-right
+ = render 'layouts/header/current_user_dropdown'
= yield
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index f398d97028b..0f709c65d0e 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -11,10 +11,10 @@
.md-header
%ul.nav.nav-tabs.nav-links.clearfix
%li.md-header-tab.active
- %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 }
+ %button.js-md-write-button{ tabindex: -1 }
Write
%li.md-header-tab
- %a.js-md-preview-button{ href: "#md-preview-holder", tabindex: -1 }
+ %button.js-md-preview-button{ tabindex: -1 }
Preview
%li.md-header-toolbar.active
diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml
index 5adca007f7e..45e1d32980c 100644
--- a/app/views/projects/_wiki.html.haml
+++ b/app/views/projects/_wiki.html.haml
@@ -5,14 +5,14 @@
= render_wiki_content(@wiki_home, legacy_render_context(params))
- else
- can_create_wiki = can?(current_user, :create_wiki, @project)
- .project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] }
- .text-center{ class: container_class }
+ .landing{ class: [('row-content-block row p-0 align-items-center' if can_create_wiki), ('content-block' unless can_create_wiki)] }
+ .col-12.col-md-3.p-0
+ .svg-content
+ = image_tag 'illustrations/wiki_login_empty.svg'
+ .col-12.col-md-9.text-center.text-md-left.pl-md-0.pl-sm-3.mb-4
%h4
- This project does not have a wiki homepage yet
+ = _("This project does not have a wiki homepage yet")
- if can_create_wiki
%p
- Add a homepage to your wiki that contains information about your project
- %p
- We recommend you
- = link_to "add a homepage", project_wiki_path(@project, :home)
- to your project's wiki and GitLab will show it here instead of this message.
+ = _("Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message.")
+ = link_to _("Create your first page"), project_wiki_path(@project, :home) + '?view=create', class: "btn btn-primary"
diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml
index 87b165e581a..09295940529 100644
--- a/app/views/projects/artifacts/browse.html.haml
+++ b/app/views/projects/artifacts/browse.html.haml
@@ -1,7 +1,7 @@
- breadcrumb_title _('Artifacts')
- page_title @path.presence, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
-= render "projects/jobs/header", show_controls: false
+= render "projects/jobs/header"
- add_to_breadcrumbs(s_('CICD|Jobs'), project_jobs_path(@project))
- add_to_breadcrumbs("##{@build.id}", project_jobs_path(@project))
diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml
index f7174d6b2c6..808b4acc8f3 100644
--- a/app/views/projects/artifacts/file.html.haml
+++ b/app/views/projects/artifacts/file.html.haml
@@ -1,6 +1,6 @@
- page_title @path, 'Artifacts', "#{@build.name} (##{@build.id})", 'Jobs'
-= render "projects/jobs/header", show_controls: false
+= render "projects/jobs/header"
.tree-holder
.nav-block
diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml
index 2c8dd45670f..bd46f5a4349 100644
--- a/app/views/projects/blob/_template_selectors.html.haml
+++ b/app/views/projects/blob/_template_selectors.html.haml
@@ -5,13 +5,13 @@
.template-type-selector.js-template-type-selector-wrap.hidden
= 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 qa-license-dropdown', 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: @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 qa-gitignore-dropdown', 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(@project) } } )
.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 qa-gitlab-ci-yml-dropdown', 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(@project) } } )
.dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
- = 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 } } )
+ = 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(@project) } } )
.template-selectors-undo-menu.hidden
%span.text-info Template applied
%button.btn.btn-sm.btn-info Undo
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index fdab8a53b41..3f2d96b70e5 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -24,7 +24,7 @@
= link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id, legacy_render: params[:legacy_render]) do
= editing_preview_title(@blob.name)
- = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do
+ = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit_sha', @last_commit_sha
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 39442564a2b..4be87b9e074 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -7,7 +7,7 @@
New file
= render 'template_selectors'
.file-editor
- = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do
+ = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths(@project)) do
= render 'projects/blob/editor', ref: @ref
= render 'shared/new_commit_form', placeholder: "Add new file"
diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml
index 398f76d379a..0e4b119bb54 100644
--- a/app/views/projects/branches/_panel.html.haml
+++ b/app/views/projects/branches/_panel.html.haml
@@ -9,8 +9,7 @@
.card.prepend-top-10
.card-header
- %h4.card-title
- = panel_title
+ = panel_title
%ul.content-list.all-branches
- branches.first(overview_max_branches).each do |branch|
= render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch)
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 95828626bd9..f5685d3b50d 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -48,7 +48,7 @@
- if job.try(:allow_failure)
%span.badge.badge-danger allowed to fail
- if job.schedulable?
- %span.badge.badge-info= s_('DelayedJobs|scheduled')
+ %span.badge.badge-info= s_('DelayedJobs|delayed')
- elsif job.action?
%span.badge.badge-info manual
@@ -108,7 +108,7 @@
.btn.btn-default.has-tooltip{ disabled: true,
title: job.scheduled_at }
= sprite_icon('planning')
- = duration_in_numbers(job.execute_in, true)
+ = duration_in_numbers(job.execute_in)
- confirmation_message = s_("DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes.") % { job_name: job.name }
= link_to play_project_job_path(job.project, job, return_to: request.original_url),
method: :post,
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 28998acdc13..4917f4b8903 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -10,4 +10,4 @@
noteable_data: serialize_issuable(@issue),
noteable_type: 'Issue',
target_type: 'issue',
- current_user_data: UserSerializer.new.represent(current_user, only_path: true).to_json } }
+ current_user_data: UserSerializer.new.represent(current_user, {only_path: true}, CurrentUserEntity).to_json } }
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index a678cb6f058..5374f4a1de0 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -8,12 +8,13 @@
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
- refs_path = refs_namespace_project_path(@project.namespace, @project, search: '')
- .create-mr-dropdown-wrap{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
+ .create-mr-dropdown-wrap.d-inline-block{ data: { can_create_path: can_create_path, create_mr_path: create_mr_path, create_branch_path: create_branch_path, refs_path: refs_path } }
.btn-group.unavailable
%button.btn.btn-grouped{ type: 'button', disabled: 'disabled' }
= icon('spinner', class: 'fa-spin')
%span.text
Checking branch availability…
+
.btn-group.available.hidden
%button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } }
= value
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index c39fd0063be..b50b3ca207b 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -77,11 +77,12 @@
#related-branches{ data: { url: related_branches_project_issue_path(@project, @issue) } }
// This element is filled in using JavaScript.
- .content-block.emoji-block
+ .content-block.emoji-block.emoji-block-sticky
.row
- .col-sm-8.js-noteable-awards
+ .col-md-12.col-lg-6.js-noteable-awards
= render 'award_emoji/awards_block', awardable: @issue, inline: true
- .col-sm-4.new-branch-col
+ .col-md-12.col-lg-6.new-branch-col
+ #js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } }
= render 'new_branch' unless @issue.confidential?
%section.issuable-discussion
diff --git a/app/views/projects/jobs/_header.html.haml b/app/views/projects/jobs/_header.html.haml
index e7245622b80..018ff093475 100644
--- a/app/views/projects/jobs/_header.html.haml
+++ b/app/views/projects/jobs/_header.html.haml
@@ -1,4 +1,3 @@
-- show_controls = local_assigns.fetch(:show_controls, true)
- pipeline = @build.pipeline
.content-block.build-header.top-area.page-content-header
@@ -20,12 +19,3 @@
= render "projects/jobs/user" if @build.user
= time_ago_with_tooltip(@build.created_at)
-
- - if show_controls
- .nav-controls
- - if can?(current_user, :create_issue, @project) && @build.failed?
- = link_to "New issue", new_project_issue_path(@project, issue: build_failed_issue_options), class: 'btn btn-success btn-inverted'
- - if can?(current_user, :update_build, @build) && @build.retryable?
- = link_to "Retry job", retry_project_job_path(@project, @build), class: 'btn btn-inverted-secondary', method: :post
- %button.btn.btn-default.float-right.d-block.d-sm-none.d-md-none.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" }
- = icon('angle-double-left')
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 02a088d338b..475bae887ec 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -1,52 +1,13 @@
- @no_container = true
-- add_to_breadcrumbs "Jobs", project_jobs_path(@project)
+- add_to_breadcrumbs _("Jobs"), project_jobs_path(@project)
- breadcrumb_title "##{@build.id}"
-- page_title "#{@build.name} (##{@build.id})", "Jobs"
+- page_title "#{@build.name} (##{@build.id})", _("Jobs")
- content_for :page_specific_javascripts do
= stylesheet_link_tag 'page_bundles/xterm'
%div{ class: container_class }
- .build-page.js-build-page
- #js-build-header-vue
-
- - if @build.running? || @build.has_trace?
- .build-trace-container.prepend-top-default
- .top-bar.js-top-bar
- .js-truncated-info.truncated-info.d-none.d-sm-block.float-left.hidden<
- Showing last
- %span.js-truncated-info-size.truncated-info-size><
- of log -
- %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw
-
- .controllers.float-right
- - if @build.has_trace?
- = link_to raw_project_job_path(@project, @build),
- title: 'Show complete raw',
- data: { placement: 'top', container: 'body' },
- class: 'js-raw-link-controller has-tooltip controllers-buttons' do
- = icon('file-text-o')
-
- - if @build.erasable? && can?(current_user, :erase_build, @build)
- = link_to erase_project_job_path(@project, @build),
- method: :post,
- data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' },
- title: 'Erase job log',
- class: 'has-tooltip js-erase-link controllers-buttons' do
- = icon('trash')
- .has-tooltip.controllers-buttons{ title: 'Scroll to top', data: { placement: 'top', container: 'body'} }
- %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
- = custom_icon('scroll_up')
- .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} }
- %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true }
- = custom_icon('scroll_down')
-
- = render 'shared/builds/build_output'
-
- #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } }
-
-.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'),
- runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings') } }
+ #js-job-vue-app{ 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'),
+ build_options: javascript_build_options } }
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 11a05eada30..06ee883d6dc 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -8,7 +8,7 @@
- if can_admin_label
- content_for(:header_content) do
.nav-controls
- = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success"
+ = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success qa-label-create-new"
- if labels_or_filters
#promote-label-modal
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index ef2fa8668c0..efc2d88172e 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -51,8 +51,10 @@
= tab_link_for @merge_request, :diffs do
Changes
%span.badge.badge-pill= @merge_request.diff_size
-
- #js-vue-discussion-counter
+ .d-inline-flex.flex-wrap
+ #js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@merge_request),
+ notes_filters: UserPreference.notes_filters.to_json } }
+ #js-vue-discussion-counter
.tab-content#diff-notes-app
#notes.notes.tab-pane.voting_notes
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index d99b809c387..eede8704564 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,5 +1,4 @@
-- @breadcrumb_link = dashboard_projects_path
-- breadcrumb_title "Projects"
+- @hide_breadcrumbs = true
- @hide_top_links = true
- page_title 'New Project'
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index dbb563f51ea..2575efc0981 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -13,7 +13,11 @@
= pluralize @pipeline.total_size, "job"
- if @pipeline.ref
from
- = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
+ - if @pipeline.ref_exists?
+ = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
+ - else
+ %span.ref-name
+ = @pipeline.ref
- if @pipeline.duration
in
= time_interval_in_words(@pipeline.duration)
diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml
index 9a06eca89bb..1913d06a6f8 100644
--- a/app/views/projects/protected_branches/shared/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -1,8 +1,7 @@
.protected-branches-list.js-protected-branches-list.qa-protected-branches-list
- if @protected_branches.empty?
.card-header.bg-white
- %h3.card-title.mb-0
- Protected branch (#{@protected_branches_count})
+ Protected branch (#{@protected_branches_count})
%p.settings-message.text-center
There are currently no protected branches, protect a branch with the form above.
- else
diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
index c3b8f2f8964..d617d85afc2 100644
--- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml
@@ -2,8 +2,7 @@
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-branches-settings' }
.card
.card-header
- %h3.card-title
- Protect a branch
+ Protect a branch
.card-body
= form_errors(@protected_branch)
.form-group.row
diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
index b274c73d035..cbf1938664c 100644
--- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml
@@ -2,8 +2,7 @@
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-tags-settings' }
.card
.card-header
- %h3.card-title
- Protect a tag
+ Protect a tag
.card-body
= form_errors(@protected_tag)
.form-group.row
diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml
index c3081d75fb4..382ea848243 100644
--- a/app/views/projects/protected_tags/shared/_tags_list.html.haml
+++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml
@@ -1,8 +1,7 @@
.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty?
.card-header
- %h3.card-title
- Protected tag (#{@protected_tags_count})
+ Protected tag (#{@protected_tags_count})
%p.settings-message.text-center
There are currently no protected tags, protect a tag with the form above.
- else
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index 0426f2215ad..db1f15f96b8 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -18,8 +18,7 @@
.col-lg-12
.card
.card-header
- %h4.card-title
- = s_('ContainerRegistry|How to use the Container Registry')
+ = s_('ContainerRegistry|How to use the Container Registry')
.card-body
%p
- link_token = link_to(_('personal access token'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank')
diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml
index 98d64fafe86..597c029f755 100644
--- a/app/views/projects/services/prometheus/_metrics.html.haml
+++ b/app/views/projects/services/prometheus/_metrics.html.haml
@@ -2,9 +2,8 @@
.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } }
.card-header
- %h3.card-title
- = s_('PrometheusService|Common metrics')
- %span.badge.badge-pill.js-monitored-count 0
+ = s_('PrometheusService|Common metrics')
+ %span.badge.badge-pill.js-monitored-count 0
.card-body
.loading-metrics.js-loading-metrics
%p.prepend-top-10.prepend-left-10
@@ -17,10 +16,9 @@
.card.hidden.js-panel-missing-env-vars
.card-header
- %h3.card-title
- = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
- = s_('PrometheusService|Missing environment variable')
- %span.badge.badge-pill.js-env-var-count 0
+ = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
+ = s_('PrometheusService|Missing environment variable')
+ %span.badge.badge-pill.js-env-var-count 0
.card-body.hidden
.flash-container
.flash-notice
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index 24724394259..5e6d06d980e 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -20,8 +20,9 @@
.col-sm-10.create-from
.dropdown
= hidden_field_tag :ref, default_ref
- = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
+ = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select monospace', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref
+ = icon('chevron-down')
= render 'shared/ref_dropdown', dropdown_class: 'wide'
.form-text.text-muted
= s_('TagsPage|Existing branch name, tag, or commit SHA')
diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml
index 406dccb74fb..e37fd7624be 100644
--- a/app/views/projects/tree/_tree_commit_column.html.haml
+++ b/app/views/projects/tree/_tree_commit_column.html.haml
@@ -1,2 +1,2 @@
%span.str-truncated
- = link_to_html commit.redacted_full_title_html, project_commit_path(@project, commit.id), class: 'tree-commit-link'
+ = link_to_html commit.redacted_full_title_html, project_commit_path(@project, commit.id), title: commit.redacted_full_title_html, class: 'tree-commit-link'
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 587aeafa82f..5e0523f0b96 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -1,6 +1,6 @@
.tree-content-holder.js-tree-content{ 'data-logs-path': @logs_path }
.table-holder
- %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" }
+ %table.table#tree-slider{ class: "table_#{@hex_path} tree-table qa-file-tree" }
%thead
%tr
%th= s_('ProjectFileTree|Name')
diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml
index a15bb4c4f3f..a559ce41e57 100644
--- a/app/views/projects/triggers/_index.html.haml
+++ b/app/views/projects/triggers/_index.html.haml
@@ -3,8 +3,7 @@
= render "projects/triggers/content"
.card
.card-header
- %h4.card-title
- Manage your project's triggers
+ Manage your project's triggers
.card-body
= render "projects/triggers/form", btn_text: "Add trigger"
%hr
diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml
index cbb441d7509..c156f8cbf50 100644
--- a/app/views/projects/wikis/_pages_wiki_page.html.haml
+++ b/app/views/projects/wikis/_pages_wiki_page.html.haml
@@ -2,4 +2,5 @@
= link_to wiki_page.title, project_wiki_path(@project, wiki_page)
%small (#{wiki_page.format})
.float-right
- %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe
+ - if wiki_page.last_version
+ %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 19b9744b508..fbf248c2058 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -11,8 +11,9 @@
.nav-text
%h2.wiki-page-title= @page.title.capitalize
%span.wiki-last-edit-by
- = (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
- #{time_ago_with_tooltip(@page.last_version.authored_date)}
+ - if @page.last_version
+ = (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
+ #{time_ago_with_tooltip(@page.last_version.authored_date)}
.nav-controls
= render 'main_links'
diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml
index b89045e726a..606d0f241aa 100644
--- a/app/views/shared/_field.html.haml
+++ b/app/views/shared/_field.html.haml
@@ -24,6 +24,6 @@
- elsif type == 'select'
= form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control", disabled: disabled}
- elsif type == 'password'
- = form.password_field name, autocomplete: "new-password", class: "form-control", required: value.blank? && required, disabled: disabled
+ = form.password_field name, autocomplete: "new-password", placeholder: placeholder, class: "form-control", required: value.blank? && required, disabled: disabled
- if help
%span.form-text.text-muted= help
diff --git a/app/views/shared/_user_dropdown_contributing_link.html.haml b/app/views/shared/_user_dropdown_contributing_link.html.haml
index 333d6fa3489..564d21a39be 100644
--- a/app/views/shared/_user_dropdown_contributing_link.html.haml
+++ b/app/views/shared/_user_dropdown_contributing_link.html.haml
@@ -1,5 +1,3 @@
%li
= link_to "https://about.gitlab.com/contributing", target: '_blank', class: 'text-nowrap' do
= _("Contribute to GitLab")
- = sprite_icon('external-link', size: 16)
-%li.divider
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 6eb1f8f0853..d5fb85ba0f3 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -20,7 +20,7 @@
= hidden_field_tag data_options[:field_name], use_id ? label.try(:id) : label.try(:title), id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect{ class: classes.join(' '), type: "button", data: dropdown_data }
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.qa-issuable-label{ class: classes.join(' '), type: "button", data: dropdown_data }
- apply_is_default_styles = (selected.nil? || selected.empty?) && !no_default_styles
%span.dropdown-toggle-text{ class: ("is-default" if apply_is_default_styles) }
= multi_label_name(selected, label_name)
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index c4d177361e7..cb45928d9a5 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -36,7 +36,7 @@
%button.btn.btn-link{ type: 'button' }
= sprite_icon('search')
%span
- Press Enter or click to search
+ = _('Press Enter or click to search')
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link{ type: 'button' }
@@ -61,7 +61,7 @@
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link{ type: 'button' }
- No Assignee
+ = _('No Assignee')
%li.divider.droplab-item-ignore
- if current_user
= render 'shared/issuable/user_dropdown_item',
@@ -74,13 +74,16 @@
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link{ type: 'button' }
- No Milestone
+ = _('None')
+ %li.filter-dropdown-item{ data: { value: 'any' } }
+ %button.btn.btn-link{ type: 'button' }
+ = _('Any')
%li.filter-dropdown-item{ data: { value: 'upcoming' } }
%button.btn.btn-link{ type: 'button' }
- Upcoming
+ = _('Upcoming')
%li.filter-dropdown-item{ 'data-value' => 'started' }
%button.btn.btn-link{ type: 'button' }
- Started
+ = _('Started')
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
@@ -90,7 +93,7 @@
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.btn.btn-link{ type: 'button' }
- No Label
+ = _('No Label')
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index aa136af1955..10ffe8dd37f 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -98,7 +98,7 @@
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
- .value.issuable-show-labels.dont-hide.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
+ .value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) }
- if selected_labels.any?
- selected_labels.each do |label|
= link_to_label(label, subject: issuable.project, type: issuable.to_ability_name)
diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml
index 583b33a8a1b..660ee6d5777 100644
--- a/app/views/shared/issuable/_sidebar_todo.html.haml
+++ b/app/views/shared/issuable/_sidebar_todo.html.haml
@@ -1,6 +1,6 @@
- is_collapsed = local_assigns.fetch(:is_collapsed, false)
-- mark_content = is_collapsed ? icon('check-square', class: 'todo-undone') : _('Mark todo as done')
-- todo_content = is_collapsed ? icon('plus-square') : _('Add todo')
+- mark_content = is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : _('Mark todo as done')
+- todo_content = is_collapsed ? sprite_icon('todo-add') : _('Add todo')
%button.issuable-todo-btn.js-issuable-todo{ type: 'button',
class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn float-right'),
diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml
index 335c34a4632..7619d0a2e9c 100644
--- a/app/views/shared/labels/_form.html.haml
+++ b/app/views/shared/labels/_form.html.haml
@@ -4,18 +4,18 @@
.form-group.row
= f.label :title, class: 'col-form-label col-sm-2'
.col-sm-10
- = f.text_field :title, class: "form-control", required: true, autofocus: true
+ = f.text_field :title, class: "form-control qa-label-title", required: true, autofocus: true
.form-group.row
= f.label :description, class: 'col-form-label col-sm-2'
.col-sm-10
- = f.text_field :description, class: "form-control js-quick-submit"
+ = f.text_field :description, class: "form-control js-quick-submit qa-label-description"
.form-group.row
= f.label :color, "Background color", class: 'col-form-label col-sm-2'
.col-sm-10
.input-group
.input-group-prepend
.input-group-text.label-color-preview &nbsp;
- = f.text_field :color, class: "form-control"
+ = f.text_field :color, class: "form-control qa-label-color"
.form-text.text-muted
Choose any color.
%br
@@ -30,5 +30,5 @@
- if @label.persisted?
= f.submit 'Save changes', class: 'btn btn-success js-save-button'
- else
- = f.submit 'Create label', class: 'btn btn-success js-save-button'
+ = f.submit 'Create label', class: 'btn btn-success js-save-button qa-label-create-button'
= link_to 'Cancel', back_path, class: 'btn btn-cancel'
diff --git a/app/views/shared/runners/show.html.haml b/app/views/shared/runners/show.html.haml
index 362569bfbaf..f62eed694d2 100644
--- a/app/views/shared/runners/show.html.haml
+++ b/app/views/shared/runners/show.html.haml
@@ -24,7 +24,7 @@
%td= @runner.active? ? 'Yes' : 'No'
%tr
%td Protected
- %td= @runner.active? ? _('Yes') : _('No')
+ %td= @runner.ref_protected? ? 'Yes' : 'No'
%tr
%td Can run untagged jobs
%td= @runner.run_untagged? ? 'Yes' : 'No'
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index c8a5e199674..6bc748d346e 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -1,8 +1,9 @@
- @hide_top_links = true
-- add_to_breadcrumbs "Snippets", dashboard_snippets_path
-- breadcrumb_title "New"
+- @hide_breadcrumbs = true
- page_title "New Snippet"
-%h3.page-title
- New Snippet
-%hr
-= render "shared/snippets/form", url: snippets_path(@snippet)
+
+.page-title-holder
+ %h1.page-title= _('New Snippet')
+
+.prepend-top-default
+ = render "shared/snippets/form", url: snippets_path(@snippet)
diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb
index d9df42c9e17..2965f3b1150 100644
--- a/app/workers/namespaceless_project_destroy_worker.rb
+++ b/app/workers/namespaceless_project_destroy_worker.rb
@@ -32,7 +32,5 @@ class NamespacelessProjectDestroyWorker
merge_requests = project.forked_from_project.merge_requests.opened.from_project(project)
merge_requests.update_all(state: 'closed')
-
- project.forked_project_link.destroy
end
end