summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/koding-logo.svg8
-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/blob_edit/blob_bundle.js4
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js8
-rw-r--r--app/assets/javascripts/boards/components/board.js29
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue15
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue9
-rw-r--r--app/assets/javascripts/boards/components/board_delete.js14
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue67
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue7
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js18
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue19
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue3
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue10
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue9
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js24
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue30
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.vue5
-rw-r--r--app/assets/javascripts/boards/filtered_search_boards.js3
-rw-r--r--app/assets/javascripts/boards/index.js62
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js25
-rw-r--r--app/assets/javascripts/boards/models/issue.js3
-rw-r--r--app/assets/javascripts/boards/models/list.js9
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js19
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js10
-rw-r--r--app/assets/javascripts/clusters/clusters_index.js5
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue293
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue2
-rw-r--r--app/assets/javascripts/clusters/constants.js1
-rw-r--r--app/assets/javascripts/compare_autocomplete.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/banner.vue43
-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/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/commit_item.vue4
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions_dropdown.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue8
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue10
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js4
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_collection.js13
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_factory.js9
-rw-r--r--app/assets/javascripts/dirty_submit/dirty_submit_form.js82
-rw-r--r--app/assets/javascripts/dismissable_callout.js27
-rw-r--r--app/assets/javascripts/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/due_date_select.js7
-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_actions.vue8
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue34
-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/feature_highlight/feature_highlight.js16
-rw-r--r--app/assets/javascripts/feature_highlight/feature_highlight_helper.js18
-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.js132
-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/frequent_items/components/frequent_items_search_input.vue13
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/form.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue14
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue5
-rw-r--r--app/assets/javascripts/ide/components/file_templates/bar.vue4
-rw-r--r--app/assets/javascripts/ide/components/file_templates/dropdown.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_side_bar.vue8
-rw-r--r--app/assets/javascripts/ide/components/ide_tree.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_tree_list.vue2
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue8
-rw-r--r--app/assets/javascripts/ide/components/nav_dropdown_button.vue4
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue4
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/upload.vue38
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue17
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue7
-rw-r--r--app/assets/javascripts/ide/components/shared/tokened_input.vue4
-rw-r--r--app/assets/javascripts/ide/index.js5
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff.js14
-rw-r--r--app/assets/javascripts/ide/lib/diff/diff_worker.js2
-rw-r--r--app/assets/javascripts/ide/stores/actions/merge_request.js94
-rw-r--r--app/assets/javascripts/ide/stores/actions/project.js36
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/mutations.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/actions.js3
-rw-r--r--app/assets/javascripts/issuable_index.js9
-rw-r--r--app/assets/javascripts/issue.js73
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue516
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue181
-rw-r--r--app/assets/javascripts/issue_show/components/edit_actions.vue106
-rw-r--r--app/assets/javascripts/issue_show/components/edited.vue51
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description.vue74
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description_template.vue76
-rw-r--r--app/assets/javascripts/issue_show/components/fields/title.vue18
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue142
-rw-r--r--app/assets/javascripts/issue_show/components/locked_warning.vue12
-rw-r--r--app/assets/javascripts/issue_show/stores/index.js6
-rw-r--r--app/assets/javascripts/job.js190
-rw-r--r--app/assets/javascripts/jobs/components/artifacts_block.vue42
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue38
-rw-r--r--app/assets/javascripts/jobs/components/empty_state.vue64
-rw-r--r--app/assets/javascripts/jobs/components/environments_block.vue210
-rw-r--r--app/assets/javascripts/jobs/components/erased_block.vue40
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue275
-rw-r--r--app/assets/javascripts/jobs/components/job_container_item.vue66
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue32
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue123
-rw-r--r--app/assets/javascripts/jobs/components/jobs_container.vue85
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue512
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_detail_row.vue50
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue69
-rw-r--r--app/assets/javascripts/jobs/components/trigger_block.vue40
-rw-r--r--app/assets/javascripts/jobs/index.js26
-rw-r--r--app/assets/javascripts/jobs/job_details_bundle.js74
-rw-r--r--app/assets/javascripts/jobs/store/actions.js128
-rw-r--r--app/assets/javascripts/jobs/store/getters.js12
-rw-r--r--app/assets/javascripts/jobs/store/index.js13
-rw-r--r--app/assets/javascripts/jobs/store/mutation_types.js19
-rw-r--r--app/assets/javascripts/jobs/store/mutations.js77
-rw-r--r--app/assets/javascripts/jobs/store/state.js33
-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/labels_select.js9
-rw-r--r--app/assets/javascripts/layout_nav.js60
-rw-r--r--app/assets/javascripts/lib/ace.js1
-rw-r--r--app/assets/javascripts/lib/utils/ace_utils.js6
-rw-r--r--app/assets/javascripts/lib/utils/ajax_cache.js7
-rw-r--r--app/assets/javascripts/lib/utils/axios_utils.js23
-rw-r--r--app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js10
-rw-r--r--app/assets/javascripts/lib/utils/cache.js2
-rw-r--r--app/assets/javascripts/lib/utils/datefix.js7
-rw-r--r--app/assets/javascripts/lib/utils/notify.js4
-rw-r--r--app/assets/javascripts/lib/utils/pretty_time.js20
-rw-r--r--app/assets/javascripts/lib/utils/regexp.js3
-rw-r--r--app/assets/javascripts/lib/utils/simple_poll.js2
-rw-r--r--app/assets/javascripts/lib/utils/sticky.js24
-rw-r--r--app/assets/javascripts/lib/utils/text_markdown.js74
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js22
-rw-r--r--app/assets/javascripts/lib/utils/tick_formats.js2
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js25
-rw-r--r--app/assets/javascripts/line_highlighter.js28
-rw-r--r--app/assets/javascripts/locale/sprintf.js2
-rw-r--r--app/assets/javascripts/main.js1
-rw-r--r--app/assets/javascripts/member_expiration_date.js8
-rw-r--r--app/assets/javascripts/merge_conflicts/components/diff_file_editor.js20
-rw-r--r--app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js2
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js51
-rw-r--r--app/assets/javascripts/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/milestone_select.js7
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js17
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue11
-rw-r--r--app/assets/javascripts/monitoring/components/graph/axis.vue21
-rw-r--r--app/assets/javascripts/monitoring/components/graph/flag.vue3
-rw-r--r--app/assets/javascripts/monitoring/components/graph/track_info.vue1
-rw-r--r--app/assets/javascripts/monitoring/components/graph/track_line.vue1
-rw-r--r--app/assets/javascripts/monitoring/mixins/monitoring_mixins.js4
-rw-r--r--app/assets/javascripts/monitoring/services/monitoring_service.js49
-rw-r--r--app/assets/javascripts/monitoring/utils/measurements.js6
-rw-r--r--app/assets/javascripts/monitoring/utils/multiple_time_series.js7
-rw-r--r--app/assets/javascripts/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/notebook/cells/code/index.vue72
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue129
-rw-r--r--app/assets/javascripts/notebook/cells/output/html.vue44
-rw-r--r--app/assets/javascripts/notebook/cells/output/image.vue28
-rw-r--r--app/assets/javascripts/notebook/cells/output/index.vue128
-rw-r--r--app/assets/javascripts/notebook/cells/prompt.vue44
-rw-r--r--app/assets/javascripts/notebook/index.vue77
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue1
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue14
-rw-r--r--app/assets/javascripts/notifications_dropdown.js4
-rw-r--r--app/assets/javascripts/notifications_form.js10
-rw-r--r--app/assets/javascripts/pager.js33
-rw-r--r--app/assets/javascripts/pages/groups/edit/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/jobs/show/index.js2
-rw-r--r--app/assets/javascripts/pages/root/index.js5
-rw-r--r--app/assets/javascripts/pages/users/user_overview_block.js15
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js29
-rw-r--r--app/assets/javascripts/pdf/index.vue103
-rw-r--r--app/assets/javascripts/pdf/page/index.vue103
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue15
-rw-r--r--app/assets/javascripts/persistent_user_callout.js34
-rw-r--r--app/assets/javascripts/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/right_sidebar.js2
-rw-r--r--app/assets/javascripts/settings_panels.js16
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue3
-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.vue83
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue32
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue56
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue52
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue36
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue50
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue50
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue22
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue50
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue32
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue28
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue142
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue206
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue26
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue68
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue14
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue142
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue44
-rw-r--r--app/assets/javascripts/vue_shared/components/bar_chart.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_badge_link.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/deprecated_modal.vue150
-rw-r--r--app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js5
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue143
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue31
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/issue/issue_warning.vue51
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue56
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue236
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue117
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue48
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue72
-rw-r--r--app/assets/javascripts/vue_shared/components/memory_graph.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue68
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue32
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/panel_resizer.vue156
-rw-r--r--app/assets/javascripts/vue_shared/components/pikaday.vue106
-rw-r--r--app/assets/javascripts/vue_shared/components/project_avatar/image.vue123
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue94
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue182
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue206
-rw-r--r--app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/toggle_button.vue94
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue2
-rw-r--r--app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js19
-rw-r--r--app/assets/javascripts/vue_shared/models/label.js2
-rw-r--r--app/assets/javascripts/vue_shared/translate.js9
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js2
-rw-r--r--app/assets/javascripts/zen_mode.js32
-rw-r--r--app/assets/stylesheets/application.scss2
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/blocks.scss10
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss16
-rw-r--r--app/assets/stylesheets/framework/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/mixins.scss9
-rw-r--r--app/assets/stylesheets/framework/page_title.scss18
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss15
-rw-r--r--app/assets/stylesheets/framework/variables.scss2
-rw-r--r--app/assets/stylesheets/pages/boards.scss3
-rw-r--r--app/assets/stylesheets/pages/builds.scss2
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss2
-rw-r--r--app/assets/stylesheets/pages/diff.scss17
-rw-r--r--app/assets/stylesheets/pages/environments.scss1
-rw-r--r--app/assets/stylesheets/pages/groups.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss16
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/settings.scss4
-rw-r--r--app/assets/stylesheets/performance_bar.scss4
-rw-r--r--app/controllers/admin/applications_controller.rb8
-rw-r--r--app/controllers/admin/dashboard_controller.rb4
-rw-r--r--app/controllers/admin/health_check_controller.rb7
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/autocomplete_controller.rb2
-rw-r--r--app/controllers/boards/issues_controller.rb17
-rw-r--r--app/controllers/concerns/sends_blob.rb2
-rw-r--r--app/controllers/health_controller.rb11
-rw-r--r--app/controllers/import/github_controller.rb2
-rw-r--r--app/controllers/koding_controller.rb17
-rw-r--r--app/controllers/profiles/keys_controller.rb2
-rw-r--r--app/controllers/projects/artifacts_controller.rb4
-rw-r--r--app/controllers/projects/avatars_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb20
-rw-r--r--app/controllers/projects/build_artifacts_controller.rb4
-rw-r--r--app/controllers/projects/clusters/applications_controller.rb36
-rw-r--r--app/controllers/projects/clusters_controller.rb10
-rw-r--r--app/controllers/projects/merge_requests_controller.rb43
-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/controllers/users_controller.rb4
-rw-r--r--app/finders/applications_finder.rb22
-rw-r--r--app/finders/branches_finder.rb41
-rw-r--r--app/finders/issuable_finder.rb4
-rw-r--r--app/finders/projects_finder.rb27
-rw-r--r--app/finders/user_finder.rb52
-rw-r--r--app/finders/users_finder.rb4
-rw-r--r--app/helpers/application_settings_helper.rb39
-rw-r--r--app/helpers/blob_helper.rb22
-rw-r--r--app/helpers/ci_status_helper.rb2
-rw-r--r--app/helpers/count_helper.rb14
-rw-r--r--app/helpers/dashboard_helper.rb23
-rw-r--r--app/helpers/preferences_helper.rb36
-rw-r--r--app/helpers/projects_helper.rb16
-rw-r--r--app/helpers/sorting_helper.rb14
-rw-r--r--app/helpers/storage_health_helper.rb34
-rw-r--r--app/helpers/time_helper.rb16
-rw-r--r--app/helpers/workhorse_helper.rb2
-rw-r--r--app/mailers/notify.rb1
-rw-r--r--app/models/application_setting.rb26
-rw-r--r--app/models/blob.rb2
-rw-r--r--app/models/ci/job_artifact.rb17
-rw-r--r--app/models/clusters/applications/jupyter.rb2
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb5
-rw-r--r--app/models/clusters/concerns/application_core.rb2
-rw-r--r--app/models/clusters/concerns/application_status.rb4
-rw-r--r--app/models/concerns/blob_like.rb2
-rw-r--r--app/models/concerns/relative_positioning.rb79
-rw-r--r--app/models/deployment.rb11
-rw-r--r--app/models/environment.rb2
-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/project.rb39
-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/microsoft_teams_service.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb9
-rw-r--r--app/models/repository.rb37
-rw-r--r--app/models/tree.rb51
-rw-r--r--app/models/user.rb4
-rw-r--r--app/models/user_callout.rb3
-rw-r--r--app/models/wiki_page.rb4
-rw-r--r--app/presenters/ci/build_runner_presenter.rb10
-rw-r--r--app/presenters/project_presenter.rb52
-rw-r--r--app/serializers/build_details_entity.rb3
-rw-r--r--app/serializers/environment_status_entity.rb62
-rw-r--r--app/serializers/environment_status_serializer.rb5
-rw-r--r--app/serializers/issue_entity.rb2
-rw-r--r--app/serializers/merge_request_widget_entity.rb2
-rw-r--r--app/services/applications/create_service.rb1
-rw-r--r--app/services/audit_event_service.rb26
-rw-r--r--app/services/boards/lists/list_service.rb2
-rw-r--r--app/services/clusters/applications/create_service.rb68
-rw-r--r--app/services/clusters/applications/schedule_installation_service.rb10
-rw-r--r--app/services/clusters/create_service.rb34
-rw-r--r--app/services/clusters/update_service.rb8
-rw-r--r--app/services/groups/update_service.rb6
-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/quick_actions/interpret_service.rb2
-rw-r--r--app/services/system_note_service.rb2
-rw-r--r--app/services/web_hook_service.rb8
-rw-r--r--app/views/admin/application_settings/_koding.html.haml22
-rw-r--r--app/views/admin/application_settings/_repository_storage.html.haml27
-rw-r--r--app/views/admin/application_settings/repository.html.haml2
-rw-r--r--app/views/admin/application_settings/show.html.haml12
-rw-r--r--app/views/admin/applications/show.html.haml2
-rw-r--r--app/views/admin/dashboard/index.html.haml2
-rw-r--r--app/views/admin/groups/_form.html.haml4
-rw-r--r--app/views/admin/groups/show.html.haml2
-rw-r--r--app/views/admin/health_check/_failing_storages.html.haml15
-rw-r--r--app/views/admin/health_check/show.html.haml3
-rw-r--r--app/views/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/activity.html.haml3
-rw-r--r--app/views/dashboard/groups/index.html.haml3
-rw-r--r--app/views/dashboard/issues.html.haml9
-rw-r--r--app/views/dashboard/merge_requests.html.haml10
-rw-r--r--app/views/dashboard/milestones/index.html.haml12
-rw-r--r--app/views/dashboard/projects/index.html.haml3
-rw-r--r--app/views/dashboard/projects/starred.html.haml3
-rw-r--r--app/views/dashboard/todos/index.html.haml4
-rw-r--r--app/views/devise/shared/_omniauth_box.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml14
-rw-r--r--app/views/doorkeeper/applications/show.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml5
-rw-r--r--app/views/explore/projects/index.html.haml3
-rw-r--r--app/views/explore/projects/starred.html.haml3
-rw-r--r--app/views/explore/projects/trending.html.haml3
-rw-r--r--app/views/groups/edit.html.haml26
-rw-r--r--app/views/groups/new.html.haml5
-rw-r--r--app/views/groups/settings/_advanced.html.haml20
-rw-r--r--app/views/groups/settings/_general.html.haml48
-rw-r--r--app/views/groups/settings/_lfs.html.haml15
-rw-r--r--app/views/groups/settings/_permissions.html.haml37
-rw-r--r--app/views/groups/settings/_two_factor_auth.html.haml16
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/koding/index.html.haml6
-rw-r--r--app/views/layouts/_page.html.haml1
-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/koding.html.haml5
-rw-r--r--app/views/layouts/nav/_breadcrumbs.html.haml1
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml1
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml6
-rw-r--r--app/views/projects/_home_panel.html.haml2
-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.haml10
-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/buttons/_dropdown.html.haml4
-rw-r--r--app/views/projects/buttons/_koding.html.haml3
-rw-r--r--app/views/projects/ci/builds/_build.html.haml4
-rw-r--r--app/views/projects/clusters/_banner.html.haml4
-rw-r--r--app/views/projects/clusters/_gcp_signup_offer_banner.html.haml4
-rw-r--r--app/views/projects/deploy_tokens/_form.html.haml10
-rw-r--r--app/views/projects/deploy_tokens/_index.html.haml2
-rw-r--r--app/views/projects/deploy_tokens/_new_deploy_token.html.haml6
-rw-r--r--app/views/projects/jobs/_header.html.haml10
-rw-r--r--app/views/projects/jobs/show.html.haml49
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/new.html.haml3
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml4
-rw-r--r--app/views/projects/project_members/_team.html.haml2
-rw-r--r--app/views/projects/services/prometheus/_configuration_banner.html.haml2
-rw-r--r--app/views/projects/show.html.haml4
-rw-r--r--app/views/projects/tree/_tree_content.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml2
-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/_allow_request_access.html.haml6
-rw-r--r--app/views/shared/_field.html.haml2
-rw-r--r--app/views/shared/_old_visibility_level.html.haml6
-rw-r--r--app/views/shared/_user_dropdown_contributing_link.html.haml2
-rw-r--r--app/views/shared/_visibility_level.html.haml30
-rw-r--r--app/views/shared/empty_states/_wikis.html.haml2
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/labels/_form.html.haml8
-rw-r--r--app/views/shared/projects/_list.html.haml3
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--app/views/snippets/new.html.haml13
-rw-r--r--app/workers/namespaceless_project_destroy_worker.rb2
534 files changed, 8343 insertions, 7488 deletions
diff --git a/app/assets/images/koding-logo.svg b/app/assets/images/koding-logo.svg
deleted file mode 100644
index ad89d684d94..00000000000
--- a/app/assets/images/koding-logo.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14">
- <g fill="#d6d7d9">
- <path d="M8.7 0L5.3.3l3.2 6.8-3.2 6.6 3.5.3L12 6.9z"/>
- <ellipse cx="1.7" cy="11.1" rx="1.7" ry="1.7"/>
- <ellipse cx="1.7" cy="5.6" rx="1.7" ry="1.7"/>
- </g>
-</svg> \ No newline at end of file
diff --git a/app/assets/javascripts/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..56f64f934a1 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' */ 'blackst0ne-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 &#8984;-Enter' :
- 'You can also press Ctrl-Enter';
+ const $this = $(this);
+ const title = isMac() ? 'You can also press &#8984;-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/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 4e4598870fa..3cc89ff1955 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -13,11 +13,11 @@ export default () => {
if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relativeUrlRoot');
const assetsPath = editBlobForm.data('assetsPrefix');
- const blobLanguage = editBlobForm.data('blobLanguage');
+ const filePath = editBlobForm.data('blobFilename')
const currentAction = $('.js-file-title').data('currentAction');
const projectId = editBlobForm.data('project-id');
- new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage, currentAction, projectId);
+ new EditBlob(`${urlRoot}${assetsPath}`, filePath, currentAction, projectId);
new NewCommitForm(editBlobForm);
}
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index ec2b130ab7d..6e19548eed2 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -5,6 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { __ } from '~/locale';
import TemplateSelectorMediator from '../blob/file_template_mediator';
+import getModeByFileExtension from '~/lib/utils/ace_utils';
export default class EditBlob {
constructor(assetsPath, aceMode, currentAction, projectId) {
@@ -14,9 +15,10 @@ export default class EditBlob {
this.initFileSelectors(currentAction, projectId);
}
- configureAceEditor(aceMode, assetsPath) {
+ configureAceEditor(filePath, assetsPath) {
ace.config.set('modePath', `${assetsPath}/ace`);
ace.config.loadModule('ace/ext/searchbox');
+ ace.config.loadModule('ace/ext/modelist');
this.editor = ace.edit('editor');
@@ -25,8 +27,8 @@ export default class EditBlob {
this.editor.focus();
- if (aceMode) {
- this.editor.getSession().setMode(`ace/mode/${aceMode}`);
+ if (filePath) {
+ this.editor.getSession().setMode(getModeByFileExtension(filePath));
}
}
diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js
index 1fc7a29f785..75477ebb3b3 100644
--- a/app/assets/javascripts/boards/components/board.js
+++ b/app/assets/javascripts/boards/components/board.js
@@ -4,20 +4,17 @@ import { n__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import AccessorUtilities from '../../lib/utils/accessor';
-import boardList from './board_list.vue';
import BoardBlankState from './board_blank_state.vue';
-import './board_delete';
+import BoardDelete from './board_delete';
+import BoardList from './board_list.vue';
+import boardsStore from '../stores/boards_store';
+import { getBoardSortableDefaultOptions, sortableEnd } from '../mixins/sortable_default_options';
-const Store = gl.issueBoards.BoardsStore;
-
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.Board = Vue.extend({
+export default Vue.extend({
components: {
- boardList,
- 'board-delete': gl.issueBoards.BoardDelete,
BoardBlankState,
+ BoardDelete,
+ BoardList,
Icon,
},
directives: {
@@ -47,8 +44,8 @@ gl.issueBoards.Board = Vue.extend({
},
data () {
return {
- detailIssue: Store.detail,
- filter: Store.filter,
+ detailIssue: boardsStore.detail,
+ filter: boardsStore.filter,
};
},
computed: {
@@ -70,20 +67,20 @@ gl.issueBoards.Board = Vue.extend({
}
},
mounted () {
- this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
+ this.sortableOptions = getBoardSortableDefaultOptions({
disabled: this.disabled,
group: 'boards',
draggable: '.is-draggable',
handle: '.js-board-handle',
onEnd: (e) => {
- gl.issueBoards.onEnd();
+ sortableEnd();
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
const order = this.sortable.toArray();
- const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
+ const list = boardsStore.findList('id', parseInt(e.item.dataset.id, 10));
this.$nextTick(() => {
- Store.moveList(list, order);
+ boardsStore.moveList(list, order);
});
}
}
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index cde22725a89..38aaec73d7d 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -2,8 +2,7 @@
/* global ListLabel */
import _ from 'underscore';
import Cookies from 'js-cookie';
-
-const Store = gl.issueBoards.BoardsStore;
+import boardsStore from '../stores/boards_store';
export default {
data() {
@@ -19,7 +18,7 @@ export default {
this.clearBlankState();
this.predefinedLabels.forEach((label, i) => {
- Store.addList({
+ boardsStore.addList({
title: label.title,
position: i,
list_type: 'label',
@@ -30,14 +29,14 @@ export default {
});
});
- Store.state.lists = _.sortBy(Store.state.lists, 'position');
+ boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
// Save the labels
gl.boardService.generateDefaultLists()
.then(res => res.data)
.then((data) => {
data.forEach((listObj) => {
- const list = Store.findList('title', listObj.title);
+ const list = boardsStore.findList('title', listObj.title);
list.id = listObj.id;
list.label.id = listObj.label.id;
@@ -48,14 +47,14 @@ export default {
});
})
.catch(() => {
- Store.removeList(undefined, 'label');
+ boardsStore.removeList(undefined, 'label');
Cookies.remove('issue_board_welcome_hidden', {
path: '',
});
- Store.addBlankState();
+ boardsStore.addBlankState();
});
},
- clearBlankState: Store.removeBlankState.bind(Store),
+ clearBlankState: boardsStore.removeBlankState.bind(boardsStore),
},
};
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 0398102ad02..843498f0d06 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -2,8 +2,7 @@
/* eslint-disable vue/require-default-prop */
import IssueCardInner from './issue_card_inner.vue';
import eventHub from '../eventhub';
-
- const Store = gl.issueBoards.BoardsStore;
+ import boardsStore from '../stores/boards_store';
export default {
name: 'BoardsIssueCard',
@@ -42,7 +41,7 @@
data() {
return {
showDetail: false,
- detailIssue: Store.detail,
+ detailIssue: boardsStore.detail,
};
},
computed: {
@@ -63,11 +62,11 @@
if (this.showDetail) {
this.showDetail = false;
- if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
+ if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) {
eventHub.$emit('clearDetailIssue');
} else {
eventHub.$emit('newDetailIssue', this.issue);
- Store.detail.list = this.list;
+ boardsStore.detail.list = this.list;
}
}
},
diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js
index 240d0911a31..a5f9d65e4d5 100644
--- a/app/assets/javascripts/boards/components/board_delete.js
+++ b/app/assets/javascripts/boards/components/board_delete.js
@@ -1,12 +1,7 @@
-/* eslint-disable no-alert */
-
import $ from 'jquery';
import Vue from 'vue';
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.BoardDelete = Vue.extend({
+export default Vue.extend({
props: {
list: {
type: Object,
@@ -14,12 +9,13 @@ gl.issueBoards.BoardDelete = Vue.extend({
},
},
methods: {
- deleteBoard () {
+ deleteBoard() {
$(this.$el).tooltip('hide');
+ // eslint-disable-next-line no-alert
if (window.confirm('Are you sure you want to delete this list?')) {
this.list.destroy();
}
- }
- }
+ },
+ },
});
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 7ddb22ad824..4dc56c670f0 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -3,8 +3,8 @@ import Sortable from 'sortablejs';
import boardNewIssue from './board_new_issue.vue';
import boardCard from './board_card.vue';
import eventHub from '../eventhub';
-
-const Store = gl.issueBoards.BoardsStore;
+import boardsStore from '../stores/boards_store';
+import { getBoardSortableDefaultOptions, sortableStart } from '../mixins/sortable_default_options';
export default {
name: 'BoardList',
@@ -46,7 +46,7 @@ export default {
data() {
return {
scrollOffset: 250,
- filters: Store.state.filters,
+ filters: boardsStore.state.filters,
showCount: false,
showIssueForm: false,
};
@@ -61,13 +61,14 @@ export default {
},
issues() {
this.$nextTick(() => {
- if (this.scrollHeight() <= this.listHeight() &&
- this.list.issuesSize > this.list.issues.length) {
+ if (
+ this.scrollHeight() <= this.listHeight() &&
+ this.list.issuesSize > this.list.issues.length
+ ) {
this.list.page += 1;
- this.list.getIssues(false)
- .catch(() => {
- // TODO: handle request error
- });
+ this.list.getIssues(false).catch(() => {
+ // TODO: handle request error
+ });
}
if (this.scrollHeight() > Math.ceil(this.listHeight())) {
@@ -83,7 +84,7 @@ export default {
eventHub.$on(`scroll-board-list-${this.list.id}`, this.scrollToTop);
},
mounted() {
- const options = gl.issueBoards.getBoardSortableDefaultOptions({
+ const options = getBoardSortableDefaultOptions({
scroll: true,
disabled: this.disabled,
filter: '.board-list-count, .is-disabled',
@@ -108,7 +109,8 @@ export default {
// So from there, we can get reference to actual container
// and thus the container type to enable Copy or Move
if (e.target) {
- const containerEl = e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list');
+ const containerEl =
+ e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list');
const toBoardType = containerEl.dataset.boardType;
const cloneActions = {
label: ['milestone', 'assignee'],
@@ -120,8 +122,9 @@ export default {
const fromBoardType = this.list.type;
// For each list we check if the destination list is
// a the list were we should clone the issue
- const shouldClone = Object.entries(cloneActions).some(entry => (
- fromBoardType === entry[0] && entry[1].includes(toBoardType)));
+ const shouldClone = Object.entries(cloneActions).some(
+ entry => fromBoardType === entry[0] && entry[1].includes(toBoardType),
+ );
if (shouldClone) {
return 'clone';
@@ -133,28 +136,36 @@ export default {
},
revertClone: true,
},
- onStart: (e) => {
+ onStart: e => {
const card = this.$refs.issue[e.oldIndex];
card.showDetail = false;
- Store.moving.list = card.list;
- Store.moving.issue = Store.moving.list.findIssue(+e.item.dataset.issueId);
+ boardsStore.moving.list = card.list;
+ boardsStore.moving.issue = boardsStore.moving.list.findIssue(+e.item.dataset.issueId);
- gl.issueBoards.onStart();
+ sortableStart();
},
- onAdd: (e) => {
- gl.issueBoards.BoardsStore
- .moveIssueToList(Store.moving.list, this.list, Store.moving.issue, e.newIndex);
+ onAdd: e => {
+ boardsStore.moveIssueToList(
+ boardsStore.moving.list,
+ this.list,
+ boardsStore.moving.issue,
+ e.newIndex,
+ );
this.$nextTick(() => {
e.item.remove();
});
},
- onUpdate: (e) => {
- const sortedArray = this.sortable.toArray()
- .filter(id => id !== '-1');
- gl.issueBoards.BoardsStore
- .moveIssueInList(this.list, Store.moving.issue, e.oldIndex, e.newIndex, sortedArray);
+ onUpdate: e => {
+ const sortedArray = this.sortable.toArray().filter(id => id !== '-1');
+ boardsStore.moveIssueInList(
+ this.list,
+ boardsStore.moving.issue,
+ e.oldIndex,
+ e.newIndex,
+ sortedArray,
+ );
},
onMove(e) {
return !e.related.classList.contains('board-list-count');
@@ -192,16 +203,14 @@ export default {
if (getIssues) {
this.list.loadingMore = true;
- getIssues
- .then(loadingDone)
- .catch(loadingDone);
+ getIssues.then(loadingDone).catch(loadingDone);
}
},
toggleForm() {
this.showIssueForm = !this.showIssueForm;
},
onScroll() {
- if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
+ if (!this.list.loadingMore && this.scrollTop() > this.scrollHeight() - this.scrollOffset) {
this.loadNextPage();
}
},
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index f7ce5128964..030288a1c9d 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -4,8 +4,7 @@ import { Button } from '@gitlab-org/gitlab-ui';
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
import ListIssue from '../models/issue';
-
-const Store = gl.issueBoards.BoardsStore;
+import boardsStore from '../stores/boards_store';
export default {
name: 'BoardNewIssue',
@@ -68,8 +67,8 @@ export default {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
$(this.$refs.submitButton).enable();
- Store.detail.issue = issue;
- Store.detail.list = this.list;
+ boardsStore.detail.issue = issue;
+ boardsStore.detail.list = this.list;
})
.catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index df7efd3fa5c..62666954de0 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -14,13 +14,9 @@ import IssuableContext from '../../issuable_context';
import LabelsSelect from '../../labels_select';
import Subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
import MilestoneSelect from '../../milestone_select';
+import boardsStore from '../stores/boards_store';
-const Store = gl.issueBoards.BoardsStore;
-
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.BoardSidebar = Vue.extend({
+export default Vue.extend({
components: {
AssigneeTitle,
Assignees,
@@ -35,7 +31,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
},
data() {
return {
- detail: Store.detail,
+ detail: boardsStore.detail,
issue: {},
list: {},
loadingAssignees: false,
@@ -117,18 +113,18 @@ gl.issueBoards.BoardSidebar = Vue.extend({
this.saveAssignees();
},
removeAssignee (a) {
- gl.issueBoards.BoardsStore.detail.issue.removeAssignee(a);
+ boardsStore.detail.issue.removeAssignee(a);
},
addAssignee (a) {
- gl.issueBoards.BoardsStore.detail.issue.addAssignee(a);
+ boardsStore.detail.issue.addAssignee(a);
},
removeAllAssignees () {
- gl.issueBoards.BoardsStore.detail.issue.removeAllAssignees();
+ boardsStore.detail.issue.removeAllAssignees();
},
saveAssignees () {
this.loadingAssignees = true;
- gl.issueBoards.BoardsStore.detail.issue.update()
+ boardsStore.detail.issue.update()
.then(() => {
this.loadingAssignees = false;
})
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index 8b5536200e1..aa98f35786e 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -1,14 +1,15 @@
<script>
import $ from 'jquery';
+ import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
import tooltip from '../../vue_shared/directives/tooltip';
-
- const Store = gl.issueBoards.BoardsStore;
+ import boardsStore from '../stores/boards_store';
export default {
components: {
UserAvatarLink,
+ Icon,
},
directives: {
tooltip,
@@ -110,7 +111,7 @@
filterByLabel(label, e) {
if (!this.updateFilters) return;
- const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
+ const filterPath = boardsStore.filter.path.split('&');
const labelTitle = encodeURIComponent(label.title);
const param = `label_name[]=${labelTitle}`;
const labelIndex = filterPath.indexOf(param);
@@ -122,9 +123,9 @@
filterPath.splice(labelIndex, 1);
}
- gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
+ boardsStore.filter.path = filterPath.join('&');
- Store.updateFiltersUrl();
+ boardsStore.updateFiltersUrl();
eventHub.$emit('updateTokens');
},
@@ -141,11 +142,11 @@
<div>
<div class="board-card-header">
<h4 class="board-card-title">
- <i
+ <icon
v-if="issue.confidential"
- class="fa fa-eye-slash confidential-icon"
- aria-hidden="true"
- ></i>
+ name="eye-slash"
+ class="confidential-icon"
+ />
<a
:href="issue.path"
:title="issue.title"
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index d4affc8c3de..268ca6bca13 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -5,6 +5,7 @@ import ListsDropdown from './lists_dropdown.vue';
import { pluralize } from '../../../lib/utils/text_utility';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
+import boardsStore from '../../stores/boards_store';
export default {
components: {
@@ -14,7 +15,7 @@ export default {
data() {
return {
modal: ModalStore.store,
- state: gl.issueBoards.BoardsStore.state,
+ state: boardsStore.state,
};
},
computed: {
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index a58b5afe970..c93fd9f415c 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -1,4 +1,5 @@
<script>
+ import Icon from '~/vue_shared/components/icon.vue';
import bp from '../../../breakpoints';
import ModalStore from '../../stores/modal_store';
import IssueCardInner from '../issue_card_inner.vue';
@@ -6,6 +7,7 @@
export default {
components: {
IssueCardInner,
+ Icon,
},
props: {
issueLinkBase: {
@@ -147,13 +149,13 @@
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath"/>
- <span
+ <icon
v-if="issue.selected"
:aria-label="'Issue #' + issue.id + ' selected'"
+ name="mobile-issue-close"
aria-checked="true"
- class="issue-card-selected text-center">
- <i class="fa fa-check"></i>
- </span>
+ class="issue-card-selected text-center"
+ />
</div>
</div>
</div>
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
index 4f23e5db35c..3baac08d411 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
@@ -1,15 +1,18 @@
<script>
import { Link } from '@gitlab-org/gitlab-ui';
+import Icon from '~/vue_shared/components/icon.vue';
import ModalStore from '../../stores/modal_store';
+import boardsStore from '../../stores/boards_store';
export default {
components: {
'gl-link': Link,
+ Icon,
},
data() {
return {
modal: ModalStore.store,
- state: gl.issueBoards.BoardsStore.state,
+ state: boardsStore.state,
};
},
computed: {
@@ -34,7 +37,9 @@ export default {
class="dropdown-label-box">
</span>
{{ selected.title }}
- <i class="fa fa-chevron-down"></i>
+ <icon
+ name="chevron-down"
+ />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 448ab9ed135..2c2045f8901 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
@@ -4,16 +4,12 @@ import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
-
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-const Store = gl.issueBoards.BoardsStore;
+import boardsStore from '../stores/boards_store';
$(document).off('created.label').on('created.label', (e, label) => {
- Store.new({
+ boardsStore.new({
title: label.title,
- position: Store.state.lists.length - 2,
+ position: boardsStore.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
@@ -23,7 +19,7 @@ $(document).off('created.label').on('created.label', (e, label) => {
});
});
-gl.issueBoards.newListDropdownInit = () => {
+export default function initNewListDropdown() {
$('.js-new-board-list').each(function () {
const $this = $(this);
new CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespacePath'), $this.data('projectPath'));
@@ -36,7 +32,7 @@ gl.issueBoards.newListDropdownInit = () => {
});
},
renderRow (label) {
- const active = Store.findList('title', label.title);
+ const active = boardsStore.findList('title', label.title);
const $li = $('<li />');
const $a = $('<a />', {
class: (active ? `is-active js-board-list-${active.id}` : ''),
@@ -62,10 +58,10 @@ gl.issueBoards.newListDropdownInit = () => {
const label = options.selectedObj;
e.preventDefault();
- if (!Store.findList('title', label.title)) {
- Store.new({
+ if (!boardsStore.findList('title', label.title)) {
+ boardsStore.new({
title: label.title,
- position: Store.state.lists.length - 2,
+ position: boardsStore.state.lists.length - 2,
list_type: 'label',
label: {
id: label.id,
@@ -74,9 +70,9 @@ gl.issueBoards.newListDropdownInit = () => {
},
});
- Store.state.lists = _.sortBy(Store.state.lists, 'position');
+ boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
}
},
});
});
-};
+}
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index d4676914e02..4e8fe16160a 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -1,11 +1,15 @@
<script>
import $ from 'jquery';
import _ from 'underscore';
+import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../eventhub';
import Api from '../../api';
export default {
name: 'BoardProjectSelect',
+ components: {
+ Icon,
+ },
props: {
groupId: {
type: Number,
@@ -78,11 +82,9 @@ export default {
aria-expanded="false"
>
{{ selectedProjectName }}
- <i
- class="fa fa-chevron-down"
- aria-hidden="true"
- >
- </i>
+ <icon
+ name="chevron-down"
+ />
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
<div class="dropdown-title">
@@ -92,12 +94,11 @@ export default {
type="button"
class="dropdown-title-button dropdown-menu-close"
>
- <i
- aria-hidden="true"
+ <icon
+ name="merge-request-close-m"
data-hidden="true"
- class="fa fa-times dropdown-menu-close-icon"
- >
- </i>
+ class="dropdown-menu-close-icon"
+ />
</button>
</div>
<div class="dropdown-input">
@@ -106,12 +107,11 @@ export default {
type="search"
placeholder="Search projects"
/>
- <i
- aria-hidden="true"
+ <icon
+ name="search"
+ class="dropdown-input-search"
data-hidden="true"
- class="fa fa-search dropdown-input-search"
- >
- </i>
+ />
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading">
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
index 90d4c710daf..b8f2e324d43 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.vue
@@ -2,8 +2,7 @@
import Vue from 'vue';
import Flash from '../../../flash';
import { __ } from '../../../locale';
-
- const Store = gl.issueBoards.BoardsStore;
+ import boardsStore from '../../stores/boards_store';
export default Vue.extend({
props: {
@@ -49,7 +48,7 @@
list.removeIssue(issue);
});
- Store.detail.issue = {};
+ boardsStore.detail.issue = {};
},
/**
* Build the default patch request.
diff --git a/app/assets/javascripts/boards/filtered_search_boards.js b/app/assets/javascripts/boards/filtered_search_boards.js
index 46d61ebbf24..acf41e5689e 100644
--- a/app/assets/javascripts/boards/filtered_search_boards.js
+++ b/app/assets/javascripts/boards/filtered_search_boards.js
@@ -1,5 +1,6 @@
import FilteredSearchContainer from '../filtered_search/container';
import FilteredSearchManager from '../filtered_search/filtered_search_manager';
+import boardsStore from './stores/boards_store';
export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
@@ -23,7 +24,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
this.store.path = path.substr(1);
if (this.updateUrl) {
- gl.issueBoards.BoardsStore.updateFiltersUrl();
+ boardsStore.updateFiltersUrl();
}
}
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index caa6ce84335..91861f2f9ee 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -14,24 +14,22 @@ import './models/issue';
import './models/list';
import './models/milestone';
import './models/project';
-import './stores/boards_store';
+import boardsStore from './stores/boards_store';
import ModalStore from './stores/modal_store';
import BoardService from './services/board_service';
import modalMixin from './mixins/modal_mixins';
-import './mixins/sortable_default_options';
import './filters/due_date_filters';
-import './components/board';
-import './components/board_sidebar';
-import './components/new_list_dropdown';
+import Board from './components/board';
+import BoardSidebar from './components/board_sidebar';
+import initNewListDropdown from './components/new_list_dropdown';
import BoardAddIssuesModal from './components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor';
import { NavigationType } from '~/lib/utils/common_utils';
+let issueBoardsApp;
+
export default () => {
const $boardApp = document.getElementById('board-app');
- const Store = gl.issueBoards.BoardsStore;
-
- window.gl = window.gl || {};
// check for browser back and trigger a hard reload to circumvent browser caching.
window.addEventListener('pageshow', (event) => {
@@ -43,25 +41,21 @@ export default () => {
}
});
- if (gl.IssueBoardsApp) {
- gl.IssueBoardsApp.$destroy(true);
+ if (issueBoardsApp) {
+ issueBoardsApp.$destroy(true);
}
- Store.create();
-
- // hack to allow sidebar scripts like milestone_select manipulate the BoardsStore
- gl.issueBoards.boardStoreIssueSet = (...args) => Vue.set(Store.detail.issue, ...args);
- gl.issueBoards.boardStoreIssueDelete = (...args) => Vue.delete(Store.detail.issue, ...args);
+ boardsStore.create();
- gl.IssueBoardsApp = new Vue({
+ issueBoardsApp = new Vue({
el: $boardApp,
components: {
- board: gl.issueBoards.Board,
- 'board-sidebar': gl.issueBoards.BoardSidebar,
+ Board,
+ BoardSidebar,
BoardAddIssuesModal,
},
data: {
- state: Store.state,
+ state: boardsStore.state,
loading: true,
boardsEndpoint: $boardApp.dataset.boardsEndpoint,
listsEndpoint: $boardApp.dataset.listsEndpoint,
@@ -70,7 +64,7 @@ export default () => {
issueLinkBase: $boardApp.dataset.issueLinkBase,
rootPath: $boardApp.dataset.rootPath,
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
- detailIssue: Store.detail,
+ detailIssue: boardsStore.detail,
defaultAvatar: $boardApp.dataset.defaultAvatar,
},
computed: {
@@ -85,7 +79,7 @@ export default () => {
bulkUpdatePath: this.bulkUpdatePath,
boardId: this.boardId,
});
- Store.rootPath = this.boardsEndpoint;
+ boardsStore.rootPath = this.boardsEndpoint;
eventHub.$on('updateTokens', this.updateTokens);
eventHub.$on('newDetailIssue', this.updateDetailIssue);
@@ -99,16 +93,16 @@ export default () => {
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
},
mounted() {
- this.filterManager = new FilteredSearchBoards(Store.filter, true, Store.cantEdit);
+ this.filterManager = new FilteredSearchBoards(boardsStore.filter, true, boardsStore.cantEdit);
this.filterManager.setup();
- Store.disabled = this.disabled;
+ boardsStore.disabled = this.disabled;
gl.boardService
.all()
.then(res => res.data)
.then(data => {
data.forEach(board => {
- const list = Store.addList(board, this.defaultAvatar);
+ const list = boardsStore.addList(board, this.defaultAvatar);
if (list.type === 'closed') {
list.position = Infinity;
@@ -119,7 +113,7 @@ export default () => {
this.state.lists = _.sortBy(this.state.lists, 'position');
- Store.addBlankState();
+ boardsStore.addBlankState();
this.loading = false;
})
.catch(() => {
@@ -148,13 +142,13 @@ export default () => {
});
}
- Store.detail.issue = newIssue;
+ boardsStore.detail.issue = newIssue;
},
clearDetailIssue() {
- Store.detail.issue = {};
+ boardsStore.detail.issue = {};
},
toggleSubscription(id) {
- const { issue } = Store.detail;
+ const { issue } = boardsStore.detail;
if (issue.id === id && issue.toggleSubscriptionEndpoint) {
issue.setFetchingState('subscriptions', true);
BoardService.toggleIssueSubscription(issue.toggleSubscriptionEndpoint)
@@ -173,26 +167,28 @@ export default () => {
},
});
- gl.IssueBoardsSearch = new Vue({
+ // eslint-disable-next-line no-new
+ new Vue({
el: document.getElementById('js-add-list'),
data: {
- filters: Store.state.filters,
+ filters: boardsStore.state.filters,
},
mounted() {
- gl.issueBoards.newListDropdownInit();
+ initNewListDropdown();
},
});
const issueBoardsModal = document.getElementById('js-add-issues-btn');
if (issueBoardsModal) {
- gl.IssueBoardsModalAddBtn = new Vue({
+ // eslint-disable-next-line no-new
+ new Vue({
el: issueBoardsModal,
mixins: [modalMixin],
data() {
return {
modal: ModalStore.store,
- store: Store.state,
+ store: boardsStore.state,
canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
};
},
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js
index a8df45fc473..c9cde4effb9 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js
@@ -3,32 +3,29 @@
import $ from 'jquery';
import sortableConfig from '../../sortable/sortable_config';
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.onStart = () => {
+export function sortableStart() {
$('.has-tooltip').tooltip('hide')
.tooltip('disable');
document.body.classList.add('is-dragging');
-};
+}
-gl.issueBoards.onEnd = () => {
+export function sortableEnd() {
$('.has-tooltip').tooltip('enable');
document.body.classList.remove('is-dragging');
-};
+}
-gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
+export function getBoardSortableDefaultOptions(obj) {
+ const touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
-gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
const defaultSortOptions = Object.assign({}, sortableConfig, {
filter: '.board-delete, .btn',
- delay: gl.issueBoards.touchEnabled ? 100 : 0,
- scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
+ delay: touchEnabled ? 100 : 0,
+ scrollSensitivity: touchEnabled ? 60 : 100,
scrollSpeed: 20,
- onStart: gl.issueBoards.onStart,
- onEnd: gl.issueBoards.onEnd,
+ onStart: sortableStart,
+ onEnd: sortableEnd,
});
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
return defaultSortOptions;
-};
+}
diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js
index 609659bdf93..52d04389b88 100644
--- a/app/assets/javascripts/boards/models/issue.js
+++ b/app/assets/javascripts/boards/models/issue.js
@@ -6,6 +6,7 @@
import Vue from 'vue';
import '~/vue_shared/models/label';
import IssueProject from './project';
+import boardsStore from '../stores/boards_store';
class ListIssue {
constructor (obj, defaultAvatar) {
@@ -86,7 +87,7 @@ class ListIssue {
}
getLists () {
- return gl.issueBoards.BoardsStore.state.lists.filter(list => list.findIssue(this.id));
+ return boardsStore.state.lists.filter(list => list.findIssue(this.id));
}
updateData(newData) {
diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js
index 58e423fbd44..3161f1da8c9 100644
--- a/app/assets/javascripts/boards/models/list.js
+++ b/app/assets/javascripts/boards/models/list.js
@@ -5,6 +5,7 @@ import { __ } from '~/locale';
import ListLabel from '~/vue_shared/models/label';
import ListAssignee from '~/vue_shared/models/assignee';
import { urlParamsToObject } from '~/lib/utils/common_utils';
+import boardsStore from '../stores/boards_store';
const PER_PAGE = 20;
@@ -89,9 +90,9 @@ class List {
}
destroy() {
- const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this);
- gl.issueBoards.BoardsStore.state.lists.splice(index, 1);
- gl.issueBoards.BoardsStore.updateNewListDropdown(this.id);
+ const index = boardsStore.state.lists.indexOf(this);
+ boardsStore.state.lists.splice(index, 1);
+ boardsStore.updateNewListDropdown(this.id);
gl.boardService.destroyList(this.id).catch(() => {
// TODO: handle request error
@@ -116,7 +117,7 @@ class List {
getIssues(emptyIssues = true) {
const data = {
- ...urlParamsToObject(gl.issueBoards.BoardsStore.filter.path),
+ ...urlParamsToObject(boardsStore.filter.path),
page: this.page,
};
diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js
index bd181807e1f..471955747fd 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js
+++ b/app/assets/javascripts/boards/stores/boards_store.js
@@ -3,13 +3,11 @@
import $ from 'jquery';
import _ from 'underscore';
+import Vue from 'vue';
import Cookies from 'js-cookie';
import { getUrlParamsArray } from '~/lib/utils/common_utils';
-window.gl = window.gl || {};
-window.gl.issueBoards = window.gl.issueBoards || {};
-
-gl.issueBoards.BoardsStore = {
+const boardsStore = {
disabled: false,
filter: {
path: '',
@@ -167,3 +165,16 @@ gl.issueBoards.BoardsStore = {
window.history.pushState(null, null, `?${this.filter.path}`);
}
};
+
+// hacks added in order to allow milestone_select to function properly
+// TODO: remove these
+
+export function boardStoreIssueSet(...args) {
+ Vue.set(boardsStore.detail.issue, ...args);
+}
+
+export function boardStoreIssueDelete(...args) {
+ Vue.delete(boardsStore.detail.issue, ...args);
+}
+
+export default boardsStore;
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 65e7cee7039..ebf76af5966 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -1,6 +1,6 @@
import Visibility from 'visibilityjs';
import Vue from 'vue';
-import PersistentUserCallout from '../persistent_user_callout';
+import initDismissableCallout from '~/dismissable_callout';
import { s__, sprintf } from '../locale';
import Flash from '../flash';
import Poll from '../lib/utils/poll';
@@ -62,7 +62,7 @@ export default class Clusters {
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
- Clusters.initDismissableCallout();
+ initDismissableCallout('.js-cluster-security-warning');
initSettingsPanels();
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
this.initApplications();
@@ -105,12 +105,6 @@ export default class Clusters {
});
}
- static initDismissableCallout() {
- const callout = document.querySelector('.js-cluster-security-warning');
-
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
- }
-
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js
index e32d507d1f7..789c8360124 100644
--- a/app/assets/javascripts/clusters/clusters_index.js
+++ b/app/assets/javascripts/clusters/clusters_index.js
@@ -1,15 +1,14 @@
import createFlash from '~/flash';
import { __ } from '~/locale';
import setupToggleButtons from '~/toggle_buttons';
-import PersistentUserCallout from '../persistent_user_callout';
+import initDismissableCallout from '~/dismissable_callout';
import ClustersService from './services/clusters_service';
export default () => {
const clusterList = document.querySelector('.js-clusters-list');
- const callout = document.querySelector('.gcp-signup-offer');
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
+ initDismissableCallout('.gcp-signup-offer');
// The empty state won't have a clusterList
if (clusterList) {
diff --git a/app/assets/javascripts/clusters/components/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/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index a1069985178..6e7b5eb5526 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -1,6 +1,6 @@
<script>
import _ from 'underscore';
-import helmInstallIllustration from '@gitlab-org/gitlab-svgs/illustrations/kubernetes-installation.svg';
+import helmInstallIllustration from '@gitlab-org/gitlab-svgs/dist/illustrations/kubernetes-installation.svg';
import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png';
import gitlabLogo from 'images/cluster_app_logos/gitlab.png';
import helmLogo from 'images/cluster_app_logos/helm.png';
diff --git a/app/assets/javascripts/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/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/cycle_analytics/components/banner.vue b/app/assets/javascripts/cycle_analytics/components/banner.vue
index 410d4873e55..82b0f523d2e 100644
--- a/app/assets/javascripts/cycle_analytics/components/banner.vue
+++ b/app/assets/javascripts/cycle_analytics/components/banner.vue
@@ -1,24 +1,28 @@
<script>
- 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 {
- props: {
- documentationLink: {
- type: String,
- required: true,
- },
+export default {
+ components: {
+ Icon,
+ },
+ 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">
@@ -28,10 +32,9 @@
type="button"
@click="dismissOverviewDialog"
>
- <i
- class="fa fa-times"
- aria-hidden="true">
- </i>
+ <icon
+ name="close"
+ />
</button>
<div
class="svg-container"
diff --git a/app/assets/javascripts/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/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/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/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
index c3acc352d5e..f4b333f3700 100644
--- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
@@ -115,7 +115,7 @@ export default {
<span>
{{ selectedVersionName }}
</span>
- <Icon
+ <icon
:size="12"
name="angle-down"
class="position-absolute"
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 4e04e50c52a..f72c7a84e5c 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -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() {
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 15b37243030..dcf1057eb84 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -20,6 +20,11 @@ export default {
Tooltip,
},
props: {
+ discussionPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
diffFile: {
type: Object,
required: true,
@@ -65,8 +70,7 @@ export default {
if (this.diffFile.submodule) {
return this.diffFile.submoduleTreeUrl || this.diffFile.submoduleLink;
}
-
- return `#${this.diffFile.fileHash}`;
+ return this.discussionPath;
},
filePath() {
if (this.diffFile.submodule) {
@@ -152,7 +156,7 @@ export default {
v-once
ref="titleWrapper"
:href="titleLink"
- class="append-right-4"
+ class="append-right-4 js-title-wrapper"
>
<file-icon
:file-name="filePath"
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index ae8930c8968..1c5c35071de 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -1,5 +1,6 @@
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
+import bp from '~/breakpoints';
import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants';
const viewTypeFromQueryString = getParameterValues('view')[0];
@@ -20,6 +21,7 @@ export default () => ({
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
tree: [],
treeEntries: {},
- showTreeList: storedTreeShow === null ? true : storedTreeShow === 'true',
+ showTreeList:
+ storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : storedTreeShow === 'true',
currentDiffFileId: '',
});
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_collection.js b/app/assets/javascripts/dirty_submit/dirty_submit_collection.js
new file mode 100644
index 00000000000..42b051b2270
--- /dev/null
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_collection.js
@@ -0,0 +1,13 @@
+import DirtySubmitForm from './dirty_submit_form';
+
+class DirtySubmitCollection {
+ constructor(forms) {
+ this.forms = forms;
+
+ this.dirtySubmits = [];
+
+ this.forms.forEach(form => this.dirtySubmits.push(new DirtySubmitForm(form)));
+ }
+}
+
+export default DirtySubmitCollection;
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_factory.js b/app/assets/javascripts/dirty_submit/dirty_submit_factory.js
new file mode 100644
index 00000000000..95a896a7f0b
--- /dev/null
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_factory.js
@@ -0,0 +1,9 @@
+import DirtySubmitCollection from './dirty_submit_collection';
+import DirtySubmitForm from './dirty_submit_form';
+
+export default function dirtySubmitFactory(formOrForms) {
+ const isCollection = formOrForms instanceof NodeList || formOrForms instanceof Array;
+ const DirtySubmitClass = isCollection ? DirtySubmitCollection : DirtySubmitForm;
+
+ return new DirtySubmitClass(formOrForms);
+}
diff --git a/app/assets/javascripts/dirty_submit/dirty_submit_form.js b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
new file mode 100644
index 00000000000..5bea47f23c5
--- /dev/null
+++ b/app/assets/javascripts/dirty_submit/dirty_submit_form.js
@@ -0,0 +1,82 @@
+import _ from 'underscore';
+
+class DirtySubmitForm {
+ constructor(form) {
+ this.form = form;
+ this.dirtyInputs = [];
+ this.isDisabled = true;
+
+ this.init();
+ }
+
+ init() {
+ this.inputs = this.form.querySelectorAll('input, textarea, select');
+ this.submits = this.form.querySelectorAll('input[type=submit], button[type=submit]');
+
+ this.inputs.forEach(DirtySubmitForm.initInput);
+ this.toggleSubmission();
+
+ this.registerListeners();
+ }
+
+ registerListeners() {
+ const throttledUpdateDirtyInput = _.throttle(
+ event => this.updateDirtyInput(event),
+ DirtySubmitForm.THROTTLE_DURATION,
+ );
+ this.form.addEventListener('input', throttledUpdateDirtyInput);
+ this.form.addEventListener('submit', event => this.formSubmit(event));
+ }
+
+ updateDirtyInput(event) {
+ const input = event.target;
+
+ if (!input.dataset.dirtySubmitOriginalValue) return;
+
+ this.updateDirtyInputs(input);
+ this.toggleSubmission();
+ }
+
+ updateDirtyInputs(input) {
+ const { name } = input;
+ const isDirty =
+ input.dataset.dirtySubmitOriginalValue !== DirtySubmitForm.inputCurrentValue(input);
+ const indexOfInputName = this.dirtyInputs.indexOf(name);
+ const isExisting = indexOfInputName !== -1;
+
+ if (isDirty && !isExisting) this.dirtyInputs.push(name);
+ if (!isDirty && isExisting) this.dirtyInputs.splice(indexOfInputName, 1);
+ }
+
+ toggleSubmission() {
+ this.isDisabled = this.dirtyInputs.length === 0;
+ this.submits.forEach(element => {
+ element.disabled = this.isDisabled;
+ });
+ }
+
+ formSubmit(event) {
+ if (this.isDisabled) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ }
+
+ return !this.isDisabled;
+ }
+
+ static initInput(element) {
+ element.dataset.dirtySubmitOriginalValue = DirtySubmitForm.inputCurrentValue(element);
+ }
+
+ static isInputCheckable(input) {
+ return input.type === 'checkbox' || input.type === 'radio';
+ }
+
+ static inputCurrentValue(input) {
+ return DirtySubmitForm.isInputCheckable(input) ? input.checked.toString() : input.value;
+ }
+}
+
+DirtySubmitForm.THROTTLE_DURATION = 500;
+
+export default DirtySubmitForm;
diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js
new file mode 100644
index 00000000000..5185b019376
--- /dev/null
+++ b/app/assets/javascripts/dismissable_callout.js
@@ -0,0 +1,27 @@
+import $ from 'jquery';
+import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
+import Flash from '~/flash';
+
+export default function initDismissableCallout(alertSelector) {
+ const alertEl = document.querySelector(alertSelector);
+ if (!alertEl) {
+ return;
+ }
+
+ const closeButtonEl = alertEl.getElementsByClassName('close')[0];
+ const { dismissEndpoint, featureId } = closeButtonEl.dataset;
+
+ closeButtonEl.addEventListener('click', () => {
+ axios
+ .post(dismissEndpoint, {
+ feature_name: featureId,
+ })
+ .then(() => {
+ $(alertEl).alert('close');
+ })
+ .catch(() => {
+ Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
+ });
+ });
+}
diff --git a/app/assets/javascripts/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/due_date_select.js b/app/assets/javascripts/due_date_select.js
index 8abd8bc581a..c7b5a35cc14 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -5,6 +5,7 @@ import { __ } from '~/locale';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
+import boardsStore from './boards/stores/boards_store';
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) {
@@ -58,7 +59,7 @@ class DueDateSelect {
$dueDateInput.val(calendar.toString(dateText));
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
- gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
+ boardsStore.detail.issue.dueDate = $dueDateInput.val();
this.updateIssueBoardIssue();
} else {
this.saveDueDate(true);
@@ -79,7 +80,7 @@ class DueDateSelect {
calendar.setDate(null);
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
- gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
+ boardsStore.detail.issue.dueDate = '';
this.updateIssueBoardIssue();
} else {
$(`input[name='${this.fieldName}']`).val('');
@@ -123,7 +124,7 @@ class DueDateSelect {
this.$loading.fadeOut();
};
- gl.issueBoards.BoardsStore.detail.issue
+ boardsStore.detail.issue
.update(this.$dropdown.attr('data-issue-update'))
.then(fadeOutLoader)
.catch(fadeOutLoader);
diff --git a/app/assets/javascripts/environments/components/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_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index 7ee2bb61007..2d51a13d8a0 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -81,11 +81,9 @@ export default {
>
<span>
<icon name="play" />
- <i
- class="fa fa-caret-down"
- aria-hidden="true"
- >
- </i>
+ <icon
+ name="chevron-down"
+ />
<gl-loading-icon v-if="isLoading" />
</span>
</button>
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index c16931521a7..f2fb0074ea1 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -4,6 +4,7 @@ import _ from 'underscore';
import tooltip from '~/vue_shared/directives/tooltip';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '~/lib/utils/text_utility';
+import Icon from '~/vue_shared/components/icon.vue';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
@@ -25,6 +26,7 @@ export default {
components: {
UserAvatarLink,
CommitComponent,
+ Icon,
ActionsComponent,
ExternalUrlComponent,
StopComponent,
@@ -435,6 +437,10 @@ export default {
this.canRetry
);
},
+
+ folderIconName() {
+ return this.model.isOpen ? 'chevron-down' : 'chevron-right';
+ },
},
methods: {
@@ -481,27 +487,15 @@ export default {
role="button"
@click="onClickFolder">
- <span class="folder-icon">
- <i
- v-show="model.isOpen"
- class="fa fa-caret-down"
- aria-hidden="true"
- >
- </i>
- <i
- v-show="!model.isOpen"
- class="fa fa-caret-right"
- aria-hidden="true"
- >
- </i>
- </span>
+ <icon
+ :name="folderIconName"
+ class="folder-icon"
+ />
- <span class="folder-icon">
- <i
- class="fa fa-folder"
- aria-hidden="true">
- </i>
- </span>
+ <icon
+ name="folder"
+ class="folder-icon"
+ />
<span>
{{ model.folderName }}
diff --git a/app/assets/javascripts/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/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/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..c4f0c41d3a8 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,47 @@ 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=%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/frequent_items/components/frequent_items_search_input.vue b/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
index a6a265eb3fd..14c223c61a4 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_search_input.vue
@@ -1,10 +1,14 @@
<script>
import _ from 'underscore';
import { mapActions } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
import frequentItemsMixin from './frequent_items_mixin';
export default {
+ components: {
+ Icon,
+ },
mixins: [frequentItemsMixin],
data() {
return {
@@ -45,11 +49,10 @@ export default {
type="search"
class="form-control"
/>
- <i
+ <icon
v-if="!searchQuery"
- class="search-icon fa fa-fw fa-search"
- aria-hidden="true"
- >
- </i>
+ name="search"
+ class="search-icon"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/form.vue b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
index ee8eb206980..802827fce76 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/form.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/form.vue
@@ -117,7 +117,7 @@ export default {
<button
:disabled="!hasChanges"
type="button"
- class="btn btn-primary btn-sm btn-block"
+ class="btn btn-primary btn-sm btn-block qa-begin-commit-button"
@click="toggleIsSmall"
>
{{ __('Commit…') }}
@@ -147,7 +147,7 @@ export default {
<loading-button
:loading="submitCommitLoading"
:label="commitButtonText"
- container-class="btn btn-success btn-sm float-left"
+ container-class="btn btn-success btn-sm float-left qa-commit-button"
@click="commitChanges"
/>
<button
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
index d376a004e84..699fa7dc937 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue
@@ -38,14 +38,18 @@ export default {
return this.modifiedFilesLength ? 'multi-file-modified' : '';
},
additionsTooltip() {
- return sprintf(n__('1 %{type} addition', '%{count} %{type} additions', this.addedFilesLength), {
- type: this.title.toLowerCase(),
- count: this.addedFilesLength,
- });
+ return sprintf(
+ n__('1 %{type} addition', '%{count} %{type} additions', this.addedFilesLength),
+ {
+ type: this.title.toLowerCase(),
+ count: this.addedFilesLength,
+ },
+ );
},
modifiedTooltip() {
return sprintf(
- n__('1 %{type} modification', '%{count} %{type} modifications', this.modifiedFilesLength), {
+ n__('1 %{type} modification', '%{count} %{type} modifications', this.modifiedFilesLength),
+ {
type: this.title.toLowerCase(),
count: this.modifiedFilesLength,
},
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
index 8a1836a5c92..adf4b479c97 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
@@ -25,10 +25,7 @@ export default {
return `discard-file-${this.path}`;
},
modalTitle() {
- return sprintf(
- __('Discard changes to %{path}?'),
- { path: this.path },
- );
+ return sprintf(__('Discard changes to %{path}?'), { path: this.path });
},
},
methods: {
diff --git a/app/assets/javascripts/ide/components/file_templates/bar.vue b/app/assets/javascripts/ide/components/file_templates/bar.vue
index 23be5f45f16..3587626c580 100644
--- a/app/assets/javascripts/ide/components/file_templates/bar.vue
+++ b/app/assets/javascripts/ide/components/file_templates/bar.vue
@@ -47,7 +47,7 @@ export default {
</script>
<template>
- <div class="d-flex align-items-center ide-file-templates">
+ <div class="d-flex align-items-center ide-file-templates qa-file-templates-bar">
<strong class="append-right-default">
{{ __('File templates') }}
</strong>
@@ -63,7 +63,7 @@ export default {
:is-async-data="true"
:searchable="true"
:title="__('File templates')"
- class="mr-2"
+ class="mr-2 qa-file-template-dropdown"
@click="selectTemplate"
/>
<transition name="fade">
diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
index ef1f6de3a86..94222c08e91 100644
--- a/app/assets/javascripts/ide/components/file_templates/dropdown.vue
+++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
@@ -92,7 +92,7 @@ export default {
v-model="search"
:placeholder="__('Filter...')"
type="search"
- class="dropdown-input-field"
+ class="dropdown-input-field qa-dropdown-filter-input"
/>
<i
aria-hidden="true"
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue
index f99ff6d6da8..dc84ee12f1e 100644
--- a/app/assets/javascripts/ide/components/ide_side_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_side_bar.vue
@@ -24,13 +24,7 @@ export default {
IdeProjectHeader,
},
computed: {
- ...mapState([
- 'loading',
- 'currentActivityView',
- 'changedFiles',
- 'stagedFiles',
- 'lastCommitMsg',
- ]),
+ ...mapState(['loading', 'currentActivityView', 'changedFiles', 'stagedFiles', 'lastCommitMsg']),
...mapGetters(['currentProject', 'someUncommitedChanges']),
showSuccessMessage() {
return (
diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue
index 39d46a91731..9f9e638f1aa 100644
--- a/app/assets/javascripts/ide/components/ide_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_tree.vue
@@ -45,7 +45,7 @@ export default {
<new-entry-button
:label="__('New file')"
:show-label="false"
- class="d-flex border-0 p-0 mr-3"
+ class="d-flex border-0 p-0 mr-3 qa-new-file"
icon="doc-new"
@click="openNewEntryModal({ type: 'blob' })"
/>
diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue
index cfe25084b42..e88f01fb4f4 100644
--- a/app/assets/javascripts/ide/components/ide_tree_list.vue
+++ b/app/assets/javascripts/ide/components/ide_tree_list.vue
@@ -43,7 +43,7 @@ export default {
<template>
<div
- class="ide-file-list"
+ class="ide-file-list qa-file-list"
>
<template v-if="showLoading">
<div
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index c8343e77860..f5e42e87f1b 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -37,14 +37,10 @@ export default {
return this.hasSearchFocus && !this.search && !this.currentSearchType;
},
type() {
- return this.currentSearchType
- ? this.currentSearchType.type
- : '';
+ return this.currentSearchType ? this.currentSearchType.type : '';
},
searchTokens() {
- return this.currentSearchType
- ? [this.currentSearchType]
- : [];
+ return this.currentSearchType ? [this.currentSearchType] : [];
},
},
watch: {
diff --git a/app/assets/javascripts/ide/components/nav_dropdown_button.vue b/app/assets/javascripts/ide/components/nav_dropdown_button.vue
index 7f98769d484..6cee4e9a8f0 100644
--- a/app/assets/javascripts/ide/components/nav_dropdown_button.vue
+++ b/app/assets/javascripts/ide/components/nav_dropdown_button.vue
@@ -13,9 +13,7 @@ export default {
computed: {
...mapState(['currentBranchId', 'currentMergeRequestId']),
mergeRequestLabel() {
- return this.currentMergeRequestId
- ? `!${this.currentMergeRequestId}`
- : EMPTY_LABEL;
+ return this.currentMergeRequestId ? `!${this.currentMergeRequestId}` : EMPTY_LABEL;
},
branchLabel() {
return this.currentBranchId || EMPTY_LABEL;
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index bcd53ac1ba2..f0a04011a3e 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -110,12 +110,12 @@ export default {
ref="fieldName"
v-model="entryName"
type="text"
- class="form-control"
+ class="form-control qa-full-file-path"
placeholder="/dir/file_name"
/>
<ul
v-if="isCreatingNew"
- class="prepend-top-default list-inline"
+ class="prepend-top-default list-inline qa-template-list"
>
<li
v-for="(template, index) in templateTypes"
diff --git a/app/assets/javascripts/ide/components/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/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index bd07f372177..10aa96dffaf 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -43,34 +43,25 @@ export default {
{
show: this.currentMergeRequestId,
title: __('Merge Request'),
- views: [
- rightSidebarViews.mergeRequestInfo,
- ],
+ views: [rightSidebarViews.mergeRequestInfo],
icon: 'text-description',
},
{
show: true,
title: __('Pipelines'),
- views: [
- rightSidebarViews.pipelines,
- rightSidebarViews.jobsDetail,
- ],
+ views: [rightSidebarViews.pipelines, rightSidebarViews.jobsDetail],
icon: 'rocket',
},
{
show: this.showLivePreview,
title: __('Live preview'),
- views: [
- rightSidebarViews.clientSidePreview,
- ],
+ views: [rightSidebarViews.clientSidePreview],
icon: 'live-preview',
},
];
},
tabs() {
- return this.defaultTabs
- .concat(this.extensionTabs)
- .filter(tab => tab.show);
+ return this.defaultTabs.concat(this.extensionTabs).filter(tab => tab.show);
},
tabViews() {
return _.flatten(this.tabs.map(tab => tab.views));
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index b2599128213..7b0f717962e 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -25,12 +25,7 @@ export default {
...mapState('rightPane', {
rightPaneIsOpen: 'isOpen',
}),
- ...mapState([
- 'rightPanelCollapsed',
- 'viewer',
- 'panelResizing',
- 'currentActivityView',
- ]),
+ ...mapState(['rightPanelCollapsed', 'viewer', 'panelResizing', 'currentActivityView']),
...mapGetters([
'currentMergeRequest',
'getStagedFile',
diff --git a/app/assets/javascripts/ide/components/shared/tokened_input.vue b/app/assets/javascripts/ide/components/shared/tokened_input.vue
index a7a12f6785d..30010957a16 100644
--- a/app/assets/javascripts/ide/components/shared/tokened_input.vue
+++ b/app/assets/javascripts/ide/components/shared/tokened_input.vue
@@ -30,9 +30,7 @@ export default {
},
computed: {
placeholderText() {
- return this.tokens.length
- ? ''
- : this.placeholder;
+ return this.tokens.length ? '' : this.placeholder;
},
},
watch: {
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index c0550116633..7a5a227db30 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -21,10 +21,7 @@ Vue.use(Translate);
export function initIde(el, options = {}) {
if (!el) return null;
- const {
- extraInitialData = () => ({}),
- rootComponent = ide,
- } = options;
+ const { extraInitialData = () => ({}), rootComponent = ide } = options;
return new Vue({
el,
diff --git a/app/assets/javascripts/ide/lib/diff/diff.js b/app/assets/javascripts/ide/lib/diff/diff.js
index 0e37f5c4704..9b7ed68b893 100644
--- a/app/assets/javascripts/ide/lib/diff/diff.js
+++ b/app/assets/javascripts/ide/lib/diff/diff.js
@@ -11,14 +11,16 @@ export const computeDiff = (originalContent, newContent) => {
if (findOnLine) {
Object.assign(findOnLine, change, {
modified: true,
- endLineNumber: (lineNumber + change.count) - 1,
+ endLineNumber: lineNumber + change.count - 1,
});
} else if ('added' in change || 'removed' in change) {
- acc.push(Object.assign({}, change, {
- lineNumber,
- modified: undefined,
- endLineNumber: (lineNumber + change.count) - 1,
- }));
+ acc.push(
+ Object.assign({}, change, {
+ lineNumber,
+ modified: undefined,
+ endLineNumber: lineNumber + change.count - 1,
+ }),
+ );
}
if (!change.removed) {
diff --git a/app/assets/javascripts/ide/lib/diff/diff_worker.js b/app/assets/javascripts/ide/lib/diff/diff_worker.js
index 78b2eab6399..77416a8de9d 100644
--- a/app/assets/javascripts/ide/lib/diff/diff_worker.js
+++ b/app/assets/javascripts/ide/lib/diff/diff_worker.js
@@ -1,7 +1,7 @@
import { computeDiff } from './diff';
// eslint-disable-next-line no-restricted-globals
-self.addEventListener('message', (e) => {
+self.addEventListener('message', e => {
const { data } = e;
// eslint-disable-next-line no-restricted-globals
diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js
index 187f8c75d07..3ac2f8b3698 100644
--- a/app/assets/javascripts/ide/stores/actions/merge_request.js
+++ b/app/assets/javascripts/ide/stores/actions/merge_request.js
@@ -116,57 +116,57 @@ export const openMergeRequest = (
targetProjectId,
mergeRequestId,
})
- .then(mr => {
- dispatch('setCurrentBranchId', mr.source_branch);
+ .then(mr => {
+ dispatch('setCurrentBranchId', mr.source_branch);
- dispatch('getBranchData', {
- projectId,
- branchId: mr.source_branch,
- });
+ dispatch('getBranchData', {
+ projectId,
+ branchId: mr.source_branch,
+ });
- return dispatch('getFiles', {
- projectId,
- branchId: mr.source_branch,
- });
- })
- .then(() =>
- dispatch('getMergeRequestVersions', {
- projectId,
- targetProjectId,
- mergeRequestId,
- }),
- )
- .then(() =>
- dispatch('getMergeRequestChanges', {
- projectId,
- targetProjectId,
- mergeRequestId,
- }),
- )
- .then(mrChanges => {
- if (mrChanges.changes.length) {
- dispatch('updateActivityBarView', activityBarViews.review);
- }
+ return dispatch('getFiles', {
+ projectId,
+ branchId: mr.source_branch,
+ });
+ })
+ .then(() =>
+ dispatch('getMergeRequestVersions', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(() =>
+ dispatch('getMergeRequestChanges', {
+ projectId,
+ targetProjectId,
+ mergeRequestId,
+ }),
+ )
+ .then(mrChanges => {
+ if (mrChanges.changes.length) {
+ dispatch('updateActivityBarView', activityBarViews.review);
+ }
- mrChanges.changes.forEach((change, ind) => {
- const changeTreeEntry = state.entries[change.new_path];
+ mrChanges.changes.forEach((change, ind) => {
+ const changeTreeEntry = state.entries[change.new_path];
- if (changeTreeEntry) {
- dispatch('setFileMrChange', {
- file: changeTreeEntry,
- mrChange: change,
- });
-
- if (ind < 10) {
- dispatch('getFileData', {
- path: change.new_path,
- makeFileActive: ind === 0,
+ if (changeTreeEntry) {
+ dispatch('setFileMrChange', {
+ file: changeTreeEntry,
+ mrChange: change,
});
+
+ if (ind < 10) {
+ dispatch('getFileData', {
+ path: change.new_path,
+ makeFileActive: ind === 0,
+ });
+ }
}
- }
+ });
+ })
+ .catch(e => {
+ flash(__('Error while loading the merge request. Please try again.'));
+ throw e;
});
- })
- .catch(e => {
- flash(__('Error while loading the merge request. Please try again.'));
- throw e;
- });
diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js
index 543dc6c0461..2cb08ab2945 100644
--- a/app/assets/javascripts/ide/stores/actions/project.js
+++ b/app/assets/javascripts/ide/stores/actions/project.js
@@ -125,10 +125,7 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
});
};
-export const openBranch = (
- { dispatch, state },
- { projectId, branchId, basePath },
-) => {
+export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath }) => {
dispatch('setCurrentBranchId', branchId);
dispatch('getBranchData', {
@@ -136,23 +133,20 @@ export const openBranch = (
branchId,
});
- return (
- dispatch('getFiles', {
- projectId,
- branchId,
- })
- .then(() => {
- if (basePath) {
- const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
- const treeEntryKey = Object.keys(state.entries).find(
- key => key === path && !state.entries[key].pending,
- );
- const treeEntry = state.entries[treeEntryKey];
+ return dispatch('getFiles', {
+ projectId,
+ branchId,
+ }).then(() => {
+ if (basePath) {
+ const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
+ const treeEntryKey = Object.keys(state.entries).find(
+ key => key === path && !state.entries[key].pending,
+ );
+ const treeEntry = state.entries[treeEntryKey];
- if (treeEntry) {
- dispatch('handleTreeEntryAction', treeEntry);
- }
+ if (treeEntry) {
+ dispatch('handleTreeEntryAction', treeEntry);
}
- })
- );
+ }
+ });
};
diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
index d519c033769..25a65b047f1 100644
--- a/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/file_templates/mutations.js
@@ -13,6 +13,7 @@ export default {
},
[types.SET_SELECTED_TEMPLATE_TYPE](state, type) {
state.selectedTemplateType = type;
+ state.templates = [];
},
[types.SET_UPDATE_SUCCESS](state, success) {
state.updateSuccess = success;
diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
index baa2497ec5b..4565c11a83f 100644
--- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js
@@ -3,8 +3,7 @@ import Api from '../../../../api';
import { scopes } from './constants';
import * as types from './mutation_types';
-export const requestMergeRequests = ({ commit }) =>
- commit(types.REQUEST_MERGE_REQUESTS);
+export const requestMergeRequests = ({ commit }) => commit(types.REQUEST_MERGE_REQUESTS);
export const receiveMergeRequestsError = ({ commit, dispatch }, { type, search }) => {
dispatch(
'setErrorMessage',
diff --git a/app/assets/javascripts/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/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index c6ad3aa3e0d..04c1cf021d9 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -1,281 +1,279 @@
<script>
- import Visibility from 'visibilityjs';
- import { visitUrl } from '../../lib/utils/url_utility';
- import Poll from '../../lib/utils/poll';
- import eventHub from '../event_hub';
- import Service from '../services/index';
- import Store from '../stores';
- import titleComponent from './title.vue';
- import descriptionComponent from './description.vue';
- import editedComponent from './edited.vue';
- import formComponent from './form.vue';
- import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
+import Visibility from 'visibilityjs';
+import { visitUrl } from '../../lib/utils/url_utility';
+import Poll from '../../lib/utils/poll';
+import eventHub from '../event_hub';
+import Service from '../services/index';
+import Store from '../stores';
+import titleComponent from './title.vue';
+import descriptionComponent from './description.vue';
+import editedComponent from './edited.vue';
+import formComponent from './form.vue';
+import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
- export default {
- components: {
- descriptionComponent,
- titleComponent,
- editedComponent,
- formComponent,
- },
- mixins: [
- recaptchaModalImplementor,
- ],
- props: {
- endpoint: {
- required: true,
- type: String,
- },
- updateEndpoint: {
- required: true,
- type: String,
- },
- canUpdate: {
- required: true,
- type: Boolean,
- },
- canDestroy: {
- required: true,
- type: Boolean,
- },
- showInlineEditButton: {
- type: Boolean,
- required: false,
- default: true,
- },
- showDeleteButton: {
- type: Boolean,
- required: false,
- default: true,
- },
- enableAutocomplete: {
- type: Boolean,
- required: false,
- default: true,
- },
- issuableRef: {
- type: String,
- required: true,
- },
- initialTitleHtml: {
- type: String,
- required: true,
- },
- initialTitleText: {
- type: String,
- required: true,
- },
- initialDescriptionHtml: {
- type: String,
- required: false,
- default: '',
- },
- initialDescriptionText: {
- type: String,
- required: false,
- default: '',
- },
- initialTaskStatus: {
- type: String,
- required: false,
- default: '',
- },
- updatedAt: {
- type: String,
- required: false,
- default: '',
- },
- updatedByName: {
- type: String,
- required: false,
- default: '',
- },
- updatedByPath: {
- type: String,
- required: false,
- default: '',
- },
- issuableTemplates: {
- type: Array,
- required: false,
- default: () => [],
- },
- markdownPreviewPath: {
- type: String,
- required: true,
- },
- markdownDocsPath: {
- type: String,
- required: true,
- },
- markdownVersion: {
- type: Number,
- required: false,
- default: 0,
- },
- projectPath: {
- type: String,
- required: true,
- },
- projectNamespace: {
- type: String,
- required: true,
- },
- issuableType: {
- type: String,
- required: false,
- default: 'issue',
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
+export default {
+ components: {
+ descriptionComponent,
+ titleComponent,
+ editedComponent,
+ formComponent,
+ },
+ mixins: [recaptchaModalImplementor],
+ props: {
+ endpoint: {
+ required: true,
+ type: String,
},
- data() {
- const store = new Store({
- titleHtml: this.initialTitleHtml,
- titleText: this.initialTitleText,
- descriptionHtml: this.initialDescriptionHtml,
- descriptionText: this.initialDescriptionText,
- updatedAt: this.updatedAt,
- updatedByName: this.updatedByName,
- updatedByPath: this.updatedByPath,
- taskStatus: this.initialTaskStatus,
- });
+ updateEndpoint: {
+ required: true,
+ type: String,
+ },
+ canUpdate: {
+ required: true,
+ type: Boolean,
+ },
+ canDestroy: {
+ required: true,
+ type: Boolean,
+ },
+ showInlineEditButton: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ showDeleteButton: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ enableAutocomplete: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ issuableRef: {
+ type: String,
+ required: true,
+ },
+ initialTitleHtml: {
+ type: String,
+ required: true,
+ },
+ initialTitleText: {
+ type: String,
+ required: true,
+ },
+ initialDescriptionHtml: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ initialDescriptionText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ initialTaskStatus: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatedAt: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatedByName: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ updatedByPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ issuableTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ markdownPreviewPath: {
+ type: String,
+ required: true,
+ },
+ markdownDocsPath: {
+ type: String,
+ required: true,
+ },
+ markdownVersion: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
+ },
+ issuableType: {
+ type: String,
+ required: false,
+ default: 'issue',
+ },
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ data() {
+ const store = new Store({
+ titleHtml: this.initialTitleHtml,
+ titleText: this.initialTitleText,
+ descriptionHtml: this.initialDescriptionHtml,
+ descriptionText: this.initialDescriptionText,
+ updatedAt: this.updatedAt,
+ updatedByName: this.updatedByName,
+ updatedByPath: this.updatedByPath,
+ taskStatus: this.initialTaskStatus,
+ });
- return {
- store,
- state: store.state,
- showForm: false,
- };
- },
- computed: {
- formState() {
- return this.store.formState;
- },
- hasUpdated() {
- return !!this.state.updatedAt;
- },
- issueChanged() {
- const descriptionChanged =
- this.initialDescriptionText !== this.store.formState.description;
- const titleChanged =
- this.initialTitleText !== this.store.formState.title;
- return descriptionChanged || titleChanged;
- },
+ return {
+ store,
+ state: store.state,
+ showForm: false,
+ };
+ },
+ computed: {
+ formState() {
+ return this.store.formState;
},
- created() {
- this.service = new Service(this.endpoint);
- this.poll = new Poll({
- resource: this.service,
- method: 'getData',
- successCallback: res => this.store.updateState(res.data),
- errorCallback(err) {
- throw new Error(err);
- },
- });
+ hasUpdated() {
+ return !!this.state.updatedAt;
+ },
+ issueChanged() {
+ const descriptionChanged = this.initialDescriptionText !== this.store.formState.description;
+ const titleChanged = this.initialTitleText !== this.store.formState.title;
+ return descriptionChanged || titleChanged;
+ },
+ },
+ created() {
+ this.service = new Service(this.endpoint);
+ this.poll = new Poll({
+ resource: this.service,
+ method: 'getData',
+ successCallback: res => this.store.updateState(res.data),
+ errorCallback(err) {
+ throw new Error(err);
+ },
+ });
+
+ if (!Visibility.hidden()) {
+ this.poll.makeRequest();
+ }
+ Visibility.change(() => {
if (!Visibility.hidden()) {
- this.poll.makeRequest();
+ this.poll.restart();
+ } else {
+ this.poll.stop();
}
+ });
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- this.poll.restart();
- } else {
- this.poll.stop();
- }
- });
-
- window.addEventListener('beforeunload', this.handleBeforeUnloadEvent);
+ window.addEventListener('beforeunload', this.handleBeforeUnloadEvent);
- eventHub.$on('delete.issuable', this.deleteIssuable);
- eventHub.$on('update.issuable', this.updateIssuable);
- eventHub.$on('close.form', this.closeForm);
- eventHub.$on('open.form', this.openForm);
- },
- beforeDestroy() {
- eventHub.$off('delete.issuable', this.deleteIssuable);
- eventHub.$off('update.issuable', this.updateIssuable);
- eventHub.$off('close.form', this.closeForm);
- eventHub.$off('open.form', this.openForm);
- window.removeEventListener('beforeunload', this.handleBeforeUnloadEvent);
- },
- methods: {
- handleBeforeUnloadEvent(e) {
- const event = e;
- if (this.showForm && this.issueChanged) {
- event.returnValue = 'Are you sure you want to lose your issue information?';
- }
- return undefined;
- },
+ eventHub.$on('delete.issuable', this.deleteIssuable);
+ eventHub.$on('update.issuable', this.updateIssuable);
+ eventHub.$on('close.form', this.closeForm);
+ eventHub.$on('open.form', this.openForm);
+ },
+ beforeDestroy() {
+ eventHub.$off('delete.issuable', this.deleteIssuable);
+ eventHub.$off('update.issuable', this.updateIssuable);
+ eventHub.$off('close.form', this.closeForm);
+ eventHub.$off('open.form', this.openForm);
+ window.removeEventListener('beforeunload', this.handleBeforeUnloadEvent);
+ },
+ methods: {
+ handleBeforeUnloadEvent(e) {
+ const event = e;
+ if (this.showForm && this.issueChanged) {
+ event.returnValue = 'Are you sure you want to lose your issue information?';
+ }
+ return undefined;
+ },
- openForm() {
- if (!this.showForm) {
- this.showForm = true;
- this.store.setFormState({
- title: this.state.titleText,
- description: this.state.descriptionText,
- lockedWarningVisible: false,
- updateLoading: false,
- });
- }
- },
- closeForm() {
- this.showForm = false;
- },
+ openForm() {
+ if (!this.showForm) {
+ this.showForm = true;
+ this.store.setFormState({
+ title: this.state.titleText,
+ description: this.state.descriptionText,
+ lockedWarningVisible: false,
+ updateLoading: false,
+ });
+ }
+ },
+ closeForm() {
+ this.showForm = false;
+ },
- updateIssuable() {
- return this.service.updateIssuable(this.store.formState)
- .then(res => res.data)
- .then(data => this.checkForSpam(data))
- .then((data) => {
- if (window.location.pathname !== data.web_url) {
- visitUrl(data.web_url);
- }
+ updateIssuable() {
+ return this.service
+ .updateIssuable(this.store.formState)
+ .then(res => res.data)
+ .then(data => this.checkForSpam(data))
+ .then(data => {
+ if (window.location.pathname !== data.web_url) {
+ visitUrl(data.web_url);
+ }
- return this.service.getData();
- })
- .then(res => res.data)
- .then((data) => {
- this.store.updateState(data);
+ return this.service.getData();
+ })
+ .then(res => res.data)
+ .then(data => {
+ this.store.updateState(data);
+ eventHub.$emit('close.form');
+ })
+ .catch(error => {
+ if (error && error.name === 'SpamError') {
+ this.openRecaptcha();
+ } else {
eventHub.$emit('close.form');
- })
- .catch((error) => {
- if (error && error.name === 'SpamError') {
- this.openRecaptcha();
- } else {
- eventHub.$emit('close.form');
- window.Flash(`Error updating ${this.issuableType}`);
- }
- });
- },
-
- closeRecaptchaModal() {
- this.store.setFormState({
- updateLoading: false,
+ window.Flash(`Error updating ${this.issuableType}`);
+ }
});
+ },
- this.closeRecaptcha();
- },
+ closeRecaptchaModal() {
+ this.store.setFormState({
+ updateLoading: false,
+ });
- deleteIssuable() {
- this.service.deleteIssuable()
- .then(res => res.data)
- .then((data) => {
- // Stop the poll so we don't get 404's with the issuable not existing
- this.poll.stop();
+ this.closeRecaptcha();
+ },
- visitUrl(data.web_url);
- })
- .catch(() => {
- eventHub.$emit('close.form');
- window.Flash(`Error deleting ${this.issuableType}`);
- });
- },
+ deleteIssuable() {
+ this.service
+ .deleteIssuable()
+ .then(res => res.data)
+ .then(data => {
+ // Stop the poll so we don't get 404's with the issuable not existing
+ this.poll.stop();
+
+ visitUrl(data.web_url);
+ })
+ .catch(() => {
+ eventHub.$emit('close.form');
+ window.Flash(`Error deleting ${this.issuableType}`);
+ });
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index 1174177f561..461cb3271b7 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,110 +1,105 @@
<script>
- import $ from 'jquery';
- import animateMixin from '../mixins/animate';
- import TaskList from '../../task_list';
- import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
+import $ from 'jquery';
+import animateMixin from '../mixins/animate';
+import TaskList from '../../task_list';
+import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
- export default {
- mixins: [
- animateMixin,
- recaptchaModalImplementor,
- ],
+export default {
+ mixins: [animateMixin, recaptchaModalImplementor],
- props: {
- canUpdate: {
- type: Boolean,
- required: true,
- },
- descriptionHtml: {
- type: String,
- required: true,
- },
- descriptionText: {
- type: String,
- required: true,
- },
- taskStatus: {
- type: String,
- required: false,
- default: '',
- },
- issuableType: {
- type: String,
- required: false,
- default: 'issue',
- },
- updateUrl: {
- type: String,
- required: false,
- default: null,
- },
+ props: {
+ canUpdate: {
+ type: Boolean,
+ required: true,
},
- data() {
- return {
- preAnimation: false,
- pulseAnimation: false,
- };
+ descriptionHtml: {
+ type: String,
+ required: true,
},
- watch: {
- descriptionHtml() {
- this.animateChange();
+ descriptionText: {
+ type: String,
+ required: true,
+ },
+ taskStatus: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ issuableType: {
+ type: String,
+ required: false,
+ default: 'issue',
+ },
+ updateUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ preAnimation: false,
+ pulseAnimation: false,
+ };
+ },
+ watch: {
+ descriptionHtml() {
+ this.animateChange();
- this.$nextTick(() => {
- this.renderGFM();
- });
- },
- taskStatus() {
- this.updateTaskStatusText();
- },
+ this.$nextTick(() => {
+ this.renderGFM();
+ });
},
- mounted() {
- this.renderGFM();
+ taskStatus() {
this.updateTaskStatusText();
},
- methods: {
- renderGFM() {
- $(this.$refs['gfm-content']).renderGFM();
+ },
+ mounted() {
+ this.renderGFM();
+ this.updateTaskStatusText();
+ },
+ methods: {
+ renderGFM() {
+ $(this.$refs['gfm-content']).renderGFM();
- if (this.canUpdate) {
- // eslint-disable-next-line no-new
- new TaskList({
- dataType: this.issuableType,
- fieldName: 'description',
- selector: '.detail-page-description',
- onSuccess: this.taskListUpdateSuccess.bind(this),
- });
- }
- },
+ if (this.canUpdate) {
+ // eslint-disable-next-line no-new
+ new TaskList({
+ dataType: this.issuableType,
+ fieldName: 'description',
+ selector: '.detail-page-description',
+ onSuccess: this.taskListUpdateSuccess.bind(this),
+ });
+ }
+ },
- taskListUpdateSuccess(data) {
- try {
- this.checkForSpam(data);
- this.closeRecaptcha();
- } catch (error) {
- if (error && error.name === 'SpamError') this.openRecaptcha();
- }
- },
+ taskListUpdateSuccess(data) {
+ try {
+ this.checkForSpam(data);
+ this.closeRecaptcha();
+ } catch (error) {
+ if (error && error.name === 'SpamError') this.openRecaptcha();
+ }
+ },
- updateTaskStatusText() {
- const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
- const $issuableHeader = $('.issuable-meta');
- const $tasks = $('#task_status', $issuableHeader);
- const $tasksShort = $('#task_status_short', $issuableHeader);
+ updateTaskStatusText() {
+ const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
+ const $issuableHeader = $('.issuable-meta');
+ const $tasks = $('#task_status', $issuableHeader);
+ const $tasksShort = $('#task_status_short', $issuableHeader);
- if (taskRegexMatches) {
- $tasks.text(this.taskStatus);
- $tasksShort.text(
- `${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ?
- 's' :
- ''}`,
- );
- } else {
- $tasks.text('');
- $tasksShort.text('');
- }
- },
+ if (taskRegexMatches) {
+ $tasks.text(this.taskStatus);
+ $tasksShort.text(
+ `${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`,
+ );
+ } else {
+ $tasks.text('');
+ $tasksShort.text('');
+ }
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue
index c3d39082714..5dda35d64bb 100644
--- a/app/assets/javascripts/issue_show/components/edit_actions.vue
+++ b/app/assets/javascripts/issue_show/components/edit_actions.vue
@@ -1,64 +1,64 @@
<script>
- import { __, sprintf } from '~/locale';
- import updateMixin from '../mixins/update';
- import eventHub from '../event_hub';
+import { __, sprintf } from '~/locale';
+import updateMixin from '../mixins/update';
+import eventHub from '../event_hub';
- const issuableTypes = {
- issue: __('Issue'),
- epic: __('Epic'),
- };
+const issuableTypes = {
+ issue: __('Issue'),
+ epic: __('Epic'),
+};
- export default {
- mixins: [updateMixin],
- props: {
- canDestroy: {
- type: Boolean,
- required: true,
- },
- formState: {
- type: Object,
- required: true,
- },
- showDeleteButton: {
- type: Boolean,
- required: false,
- default: true,
- },
- issuableType: {
- type: String,
- required: true,
- },
+export default {
+ mixins: [updateMixin],
+ props: {
+ canDestroy: {
+ type: Boolean,
+ required: true,
},
- data() {
- return {
- deleteLoading: false,
- };
+ formState: {
+ type: Object,
+ required: true,
},
- computed: {
- isSubmitEnabled() {
- return this.formState.title.trim() !== '';
- },
- shouldShowDeleteButton() {
- return this.canDestroy && this.showDeleteButton;
- },
+ showDeleteButton: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- methods: {
- closeForm() {
- eventHub.$emit('close.form');
- },
- deleteIssuable() {
- const confirmMessage = sprintf(__('%{issuableType} will be removed! Are you sure?'), {
- issuableType: issuableTypes[this.issuableType],
- });
- // eslint-disable-next-line no-alert
- if (window.confirm(confirmMessage)) {
- this.deleteLoading = true;
+ issuableType: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ deleteLoading: false,
+ };
+ },
+ computed: {
+ isSubmitEnabled() {
+ return this.formState.title.trim() !== '';
+ },
+ shouldShowDeleteButton() {
+ return this.canDestroy && this.showDeleteButton;
+ },
+ },
+ methods: {
+ closeForm() {
+ eventHub.$emit('close.form');
+ },
+ deleteIssuable() {
+ const confirmMessage = sprintf(__('%{issuableType} will be removed! Are you sure?'), {
+ issuableType: issuableTypes[this.issuableType],
+ });
+ // eslint-disable-next-line no-alert
+ if (window.confirm(confirmMessage)) {
+ this.deleteLoading = true;
- eventHub.$emit('delete.issuable');
- }
- },
+ eventHub.$emit('delete.issuable');
+ }
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/edited.vue b/app/assets/javascripts/issue_show/components/edited.vue
index 05cd976f196..73ecb26c28d 100644
--- a/app/assets/javascripts/issue_show/components/edited.vue
+++ b/app/assets/javascripts/issue_show/components/edited.vue
@@ -1,33 +1,33 @@
<script>
- import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
+import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
- export default {
- components: {
- timeAgoTooltip,
+export default {
+ components: {
+ timeAgoTooltip,
+ },
+ props: {
+ updatedAt: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- updatedAt: {
- type: String,
- required: false,
- default: '',
- },
- updatedByName: {
- type: String,
- required: false,
- default: '',
- },
- updatedByPath: {
- type: String,
- required: false,
- default: '',
- },
+ updatedByName: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- hasUpdatedBy() {
- return this.updatedByName && this.updatedByPath;
- },
+ updatedByPath: {
+ type: String,
+ required: false,
+ default: '',
},
- };
+ },
+ computed: {
+ hasUpdatedBy() {
+ return this.updatedByName && this.updatedByPath;
+ },
+ },
+};
</script>
<template>
@@ -53,4 +53,3 @@
</span>
</small>
</template>
-
diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue
index 1a78c59d715..e9e96a985a7 100644
--- a/app/assets/javascripts/issue_show/components/fields/description.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description.vue
@@ -1,45 +1,45 @@
<script>
- import updateMixin from '../../mixins/update';
- import markdownField from '../../../vue_shared/components/markdown/field.vue';
+import updateMixin from '../../mixins/update';
+import markdownField from '../../../vue_shared/components/markdown/field.vue';
- export default {
- components: {
- markdownField,
+export default {
+ components: {
+ markdownField,
+ },
+ mixins: [updateMixin],
+ props: {
+ formState: {
+ type: Object,
+ required: true,
},
- mixins: [updateMixin],
- props: {
- formState: {
- type: Object,
- required: true,
- },
- markdownPreviewPath: {
- type: String,
- required: true,
- },
- markdownDocsPath: {
- type: String,
- required: true,
- },
- markdownVersion: {
- type: Number,
- required: false,
- default: 0,
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
- enableAutocomplete: {
- type: Boolean,
- required: false,
- default: true,
- },
+ markdownPreviewPath: {
+ type: String,
+ required: true,
},
- mounted() {
- this.$refs.textarea.focus();
+ markdownDocsPath: {
+ type: String,
+ required: true,
},
- };
+ markdownVersion: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ enableAutocomplete: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ mounted() {
+ this.$refs.textarea.focus();
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/fields/description_template.vue b/app/assets/javascripts/issue_show/components/fields/description_template.vue
index e90d9fad94e..e433bf66cfc 100644
--- a/app/assets/javascripts/issue_show/components/fields/description_template.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description_template.vue
@@ -1,46 +1,46 @@
<script>
- import $ from 'jquery';
- import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
+import $ from 'jquery';
+import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
- export default {
- props: {
- formState: {
- type: Object,
- required: true,
- },
- issuableTemplates: {
- type: Array,
- required: false,
- default: () => [],
- },
- projectPath: {
- type: String,
- required: true,
- },
- projectNamespace: {
- type: String,
- required: true,
- },
+export default {
+ props: {
+ formState: {
+ type: Object,
+ required: true,
},
- computed: {
- issuableTemplatesJson() {
- return JSON.stringify(this.issuableTemplates);
- },
+ issuableTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
},
- mounted() {
- // Create the editor for the template
- const editor = document.querySelector('.detail-page-description .note-textarea') || {};
- editor.setValue = (val) => {
- this.formState.description = val;
- };
- editor.getValue = () => this.formState.description;
-
- this.issuableTemplate = new IssuableTemplateSelectors({
- $dropdowns: $(this.$refs.toggle),
- editor,
- });
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
},
- };
+ },
+ computed: {
+ issuableTemplatesJson() {
+ return JSON.stringify(this.issuableTemplates);
+ },
+ },
+ mounted() {
+ // Create the editor for the template
+ const editor = document.querySelector('.detail-page-description .note-textarea') || {};
+ editor.setValue = val => {
+ this.formState.description = val;
+ };
+ editor.getValue = () => this.formState.description;
+
+ this.issuableTemplate = new IssuableTemplateSelectors({
+ $dropdowns: $(this.$refs.toggle),
+ editor,
+ });
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/fields/title.vue b/app/assets/javascripts/issue_show/components/fields/title.vue
index b7f2b1a6050..11f4153b8d5 100644
--- a/app/assets/javascripts/issue_show/components/fields/title.vue
+++ b/app/assets/javascripts/issue_show/components/fields/title.vue
@@ -1,15 +1,15 @@
<script>
- import updateMixin from '../../mixins/update';
+import updateMixin from '../../mixins/update';
- export default {
- mixins: [updateMixin],
- props: {
- formState: {
- type: Object,
- required: true,
- },
+export default {
+ mixins: [updateMixin],
+ props: {
+ formState: {
+ type: Object,
+ required: true,
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
index 03d8d0ec67c..3b430d92912 100644
--- a/app/assets/javascripts/issue_show/components/form.vue
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -1,79 +1,79 @@
<script>
- import lockedWarning from './locked_warning.vue';
- import titleField from './fields/title.vue';
- import descriptionField from './fields/description.vue';
- import editActions from './edit_actions.vue';
- import descriptionTemplate from './fields/description_template.vue';
+import lockedWarning from './locked_warning.vue';
+import titleField from './fields/title.vue';
+import descriptionField from './fields/description.vue';
+import editActions from './edit_actions.vue';
+import descriptionTemplate from './fields/description_template.vue';
- export default {
- components: {
- lockedWarning,
- titleField,
- descriptionField,
- descriptionTemplate,
- editActions,
+export default {
+ components: {
+ lockedWarning,
+ titleField,
+ descriptionField,
+ descriptionTemplate,
+ editActions,
+ },
+ props: {
+ canDestroy: {
+ type: Boolean,
+ required: true,
},
- props: {
- canDestroy: {
- type: Boolean,
- required: true,
- },
- formState: {
- type: Object,
- required: true,
- },
- issuableTemplates: {
- type: Array,
- required: false,
- default: () => [],
- },
- issuableType: {
- type: String,
- required: true,
- },
- markdownPreviewPath: {
- type: String,
- required: true,
- },
- markdownDocsPath: {
- type: String,
- required: true,
- },
- markdownVersion: {
- type: Number,
- required: false,
- default: 0,
- },
- projectPath: {
- type: String,
- required: true,
- },
- projectNamespace: {
- type: String,
- required: true,
- },
- showDeleteButton: {
- type: Boolean,
- required: false,
- default: true,
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
- enableAutocomplete: {
- type: Boolean,
- required: false,
- default: true,
- },
+ formState: {
+ type: Object,
+ required: true,
},
- computed: {
- hasIssuableTemplates() {
- return this.issuableTemplates.length;
- },
+ issuableTemplates: {
+ type: Array,
+ required: false,
+ default: () => [],
},
- };
+ issuableType: {
+ type: String,
+ required: true,
+ },
+ markdownPreviewPath: {
+ type: String,
+ required: true,
+ },
+ markdownDocsPath: {
+ type: String,
+ required: true,
+ },
+ markdownVersion: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ projectNamespace: {
+ type: String,
+ required: true,
+ },
+ showDeleteButton: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ enableAutocomplete: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ computed: {
+ hasIssuableTemplates() {
+ return this.issuableTemplates.length;
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/components/locked_warning.vue b/app/assets/javascripts/issue_show/components/locked_warning.vue
index ad0d40faf32..0682c6f2a35 100644
--- a/app/assets/javascripts/issue_show/components/locked_warning.vue
+++ b/app/assets/javascripts/issue_show/components/locked_warning.vue
@@ -1,11 +1,11 @@
<script>
- export default {
- computed: {
- currentPath() {
- return window.location.pathname;
- },
+export default {
+ computed: {
+ currentPath() {
+ return window.location.pathname;
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/issue_show/stores/index.js b/app/assets/javascripts/issue_show/stores/index.js
index af8b0414266..32044d6da25 100644
--- a/app/assets/javascripts/issue_show/stores/index.js
+++ b/app/assets/javascripts/issue_show/stores/index.js
@@ -25,8 +25,10 @@ export default class Store {
}
stateShouldUpdate(data) {
- return this.state.titleText !== data.title_text ||
- this.state.descriptionText !== data.description_text;
+ return (
+ this.state.titleText !== data.title_text ||
+ this.state.descriptionText !== data.description_text
+ );
}
setFormState(state) {
diff --git a/app/assets/javascripts/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/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue
index d5866f9b9f1..17fd5321642 100644
--- a/app/assets/javascripts/jobs/components/artifacts_block.vue
+++ b/app/assets/javascripts/jobs/components/artifacts_block.vue
@@ -1,30 +1,28 @@
<script>
- import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
- import timeagoMixin from '~/vue_shared/mixins/timeago';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
- export default {
- components: {
- TimeagoTooltip,
+export default {
+ components: {
+ TimeagoTooltip,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ artifact: {
+ type: Object,
+ required: true,
},
- mixins: [
- timeagoMixin,
- ],
- props: {
- artifact: {
- type: Object,
- required: true,
- },
+ },
+ computed: {
+ isExpired() {
+ return this.artifact.expired;
},
- computed: {
- isExpired() {
- return this.artifact.expired;
- },
- // Only when the key is `false` we can render this block
- willExpire() {
- return this.artifact.expired === false;
- },
+ // Only when the key is `false` we can render this block
+ willExpire() {
+ return this.artifact.expired === false;
},
- };
+ },
+};
</script>
<template>
<div class="block">
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
index 4b1788a1c16..7d51f6afd10 100644
--- a/app/assets/javascripts/jobs/components/commit_block.vue
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -1,26 +1,26 @@
<script>
- import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
- export default {
- components: {
- ClipboardButton,
+export default {
+ components: {
+ ClipboardButton,
+ },
+ props: {
+ commit: {
+ type: Object,
+ required: true,
},
- props: {
- commit: {
- type: Object,
- required: true,
- },
- mergeRequest: {
- type: Object,
- required: false,
- default: null,
- },
- isLastBlock: {
- type: Boolean,
- required: true,
- },
+ mergeRequest: {
+ type: Object,
+ required: false,
+ default: null,
},
- };
+ isLastBlock: {
+ type: Boolean,
+ required: true,
+ },
+ },
+};
</script>
<template>
<div
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
index ff500eddddb..ee5ceb99b0a 100644
--- a/app/assets/javascripts/jobs/components/empty_state.vue
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -1,38 +1,38 @@
<script>
- export default {
- props: {
- illustrationPath: {
- type: String,
- required: true,
- },
- illustrationSizeClass: {
- type: String,
- required: true,
- },
- title: {
- type: String,
- required: true,
- },
- content: {
- type: String,
- required: false,
- default: null,
- },
- action: {
- type: Object,
- required: false,
- default: null,
- validator(value) {
- return (
- value === null ||
- (Object.prototype.hasOwnProperty.call(value, 'path') &&
- Object.prototype.hasOwnProperty.call(value, 'method') &&
- Object.prototype.hasOwnProperty.call(value, 'button_title'))
- );
- },
+export default {
+ props: {
+ illustrationPath: {
+ type: String,
+ required: true,
+ },
+ illustrationSizeClass: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ content: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ action: {
+ type: Object,
+ required: false,
+ default: null,
+ validator(value) {
+ return (
+ value === null ||
+ (Object.prototype.hasOwnProperty.call(value, 'path') &&
+ Object.prototype.hasOwnProperty.call(value, 'method') &&
+ Object.prototype.hasOwnProperty.call(value, 'button_title'))
+ );
},
},
- };
+ },
+};
</script>
<template>
<div class="row empty-state">
diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue
index e6e1d418194..6d1eb713886 100644
--- a/app/assets/javascripts/jobs/components/environments_block.vue
+++ b/app/assets/javascripts/jobs/components/environments_block.vue
@@ -1,129 +1,131 @@
<script>
- import _ from 'underscore';
- import CiIcon from '~/vue_shared/components/ci_icon.vue';
- import { sprintf, __ } from '../../locale';
+import _ from 'underscore';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import { sprintf, __ } from '../../locale';
- export default {
- components: {
- CiIcon,
+export default {
+ components: {
+ CiIcon,
+ },
+ props: {
+ deploymentStatus: {
+ type: Object,
+ required: true,
},
- props: {
- deploymentStatus: {
- type: Object,
- required: true,
- },
- iconStatus: {
- type: Object,
- required: true,
- },
+ iconStatus: {
+ type: Object,
+ required: true,
},
- computed: {
- environment() {
- let environmentText;
- switch (this.deploymentStatus.status) {
- case 'last':
+ },
+ computed: {
+ environment() {
+ let environmentText;
+ switch (this.deploymentStatus.status) {
+ case 'last':
+ environmentText = sprintf(
+ __('This job is the most recent deployment to %{link}.'),
+ { link: this.environmentLink },
+ false,
+ );
+ break;
+ case 'out_of_date':
+ if (this.hasLastDeployment) {
environmentText = sprintf(
- __('This job is the most recent deployment to %{link}.'),
- { link: this.environmentLink },
+ __(
+ 'This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.',
+ ),
+ {
+ environmentLink: this.environmentLink,
+ deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
+ },
false,
);
- break;
- case 'out_of_date':
- if (this.hasLastDeployment) {
- environmentText = sprintf(
- __(
- 'This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.',
- ),
- {
- environmentLink: this.environmentLink,
- deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
- },
- false,
- );
- } else {
- environmentText = sprintf(
- __('This job is an out-of-date deployment to %{environmentLink}.'),
- { environmentLink: this.environmentLink },
- false,
- );
- }
-
- break;
- case 'failed':
+ } else {
environmentText = sprintf(
- __('The deployment of this job to %{environmentLink} did not succeed.'),
+ __('This job is an out-of-date deployment to %{environmentLink}.'),
{ environmentLink: this.environmentLink },
false,
);
- break;
- case 'creating':
- if (this.hasLastDeployment) {
- environmentText = sprintf(
- __(
- 'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.',
- ),
- {
- environmentLink: this.environmentLink,
- deploymentLink: this.deploymentLink(__('latest deployment')),
- },
- false,
- );
- } else {
- environmentText = sprintf(
- __('This job is creating a deployment to %{environmentLink}.'),
- { environmentLink: this.environmentLink },
- false,
- );
- }
- break;
- default:
- break;
- }
- return environmentText;
- },
- environmentLink() {
- if (this.hasEnvironment) {
- return sprintf(
- '%{startLink}%{name}%{endLink}',
- {
- startLink: `<a href="${
- this.deploymentStatus.environment.environment_path
- }" class="js-environment-link">`,
- name: _.escape(this.deploymentStatus.environment.name),
- endLink: '</a>',
- },
+ }
+
+ break;
+ case 'failed':
+ environmentText = sprintf(
+ __('The deployment of this job to %{environmentLink} did not succeed.'),
+ { environmentLink: this.environmentLink },
false,
);
- }
- return '';
- },
- hasLastDeployment() {
- return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
- },
- lastDeployment() {
- return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
- },
- hasEnvironment() {
- return !_.isEmpty(this.deploymentStatus.environment);
- },
- lastDeploymentPath() {
- return !_.isEmpty(this.lastDeployment.deployable) ? this.lastDeployment.deployable.build_path : '';
- },
+ break;
+ case 'creating':
+ if (this.hasLastDeployment) {
+ environmentText = sprintf(
+ __(
+ 'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.',
+ ),
+ {
+ environmentLink: this.environmentLink,
+ deploymentLink: this.deploymentLink(__('latest deployment')),
+ },
+ false,
+ );
+ } else {
+ environmentText = sprintf(
+ __('This job is creating a deployment to %{environmentLink}.'),
+ { environmentLink: this.environmentLink },
+ false,
+ );
+ }
+ break;
+ default:
+ break;
+ }
+ return environmentText;
},
- methods: {
- deploymentLink(name) {
+ environmentLink() {
+ if (this.hasEnvironment) {
return sprintf(
'%{startLink}%{name}%{endLink}',
{
- startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
- name,
+ startLink: `<a href="${
+ this.deploymentStatus.environment.environment_path
+ }" class="js-environment-link">`,
+ name: _.escape(this.deploymentStatus.environment.name),
endLink: '</a>',
},
false,
);
- },
+ }
+ return '';
+ },
+ hasLastDeployment() {
+ return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
+ },
+ lastDeployment() {
+ return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
+ },
+ hasEnvironment() {
+ return !_.isEmpty(this.deploymentStatus.environment);
+ },
+ lastDeploymentPath() {
+ return !_.isEmpty(this.lastDeployment.deployable)
+ ? this.lastDeployment.deployable.build_path
+ : '';
+ },
+ },
+ methods: {
+ deploymentLink(name) {
+ return sprintf(
+ '%{startLink}%{name}%{endLink}',
+ {
+ startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
+ name,
+ endLink: '</a>',
+ },
+ false,
+ );
},
- };
+ },
+};
</script>
<template>
<div class="prepend-top-default js-environment-container">
diff --git a/app/assets/javascripts/jobs/components/erased_block.vue b/app/assets/javascripts/jobs/components/erased_block.vue
index 3d6d9ba4387..5ffbfb6e19a 100644
--- a/app/assets/javascripts/jobs/components/erased_block.vue
+++ b/app/assets/javascripts/jobs/components/erased_block.vue
@@ -1,28 +1,28 @@
<script>
- import _ from 'underscore';
- import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import _ from 'underscore';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
- export default {
- components: {
- TimeagoTooltip,
+export default {
+ components: {
+ TimeagoTooltip,
+ },
+ props: {
+ user: {
+ type: Object,
+ required: false,
+ default: () => ({}),
},
- props: {
- user: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- erasedAt: {
- type: String,
- required: true,
- },
+ erasedAt: {
+ type: String,
+ required: true,
},
- computed: {
- isErasedByUser() {
- return !_.isEmpty(this.user);
- },
+ },
+ computed: {
+ isErasedByUser() {
+ return !_.isEmpty(this.user);
},
- };
+ },
+};
</script>
<template>
<div class="prepend-top-default js-build-erased">
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 047e55866ce..ba14aaeed2c 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -1,41 +1,162 @@
<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: {
+ type: String,
+ 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',
- 'jobHasStarted',
+ 'shouldRenderTriggeredLabel',
'hasEnvironment',
'isJobStuck',
'hasTrace',
'emptyStateIllustration',
+ 'isScrollingDown',
+ 'emptyStateAction',
]),
+
+ 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="jobHasStarted"
- :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="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 -->
+ <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="runnerHelpUrl"
- />
-
- <environments-block
- v-if="hasEnvironment"
- class="js-job-environment"
- :deployment-status="job.deployment_status"
- :icon-status="job.status"
- />
-
- <erased-block
- v-if="job.erased"
- class="js-job-erased"
- :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..81cc0823792
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/job_container_item.vue
@@ -0,0 +1,66 @@
+<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';
+
+export default {
+ components: {
+ CiIcon,
+ Icon,
+ },
+
+ directives: {
+ tooltip,
+ },
+
+ props: {
+ job: {
+ type: Object,
+ required: true,
+ },
+ isActive: {
+ type: Boolean,
+ required: true,
+ },
+ },
+
+ computed: {
+ tooltipText() {
+ return `${_.escape(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 b12e963b60c..ffa6ada3e28 100644
--- a/app/assets/javascripts/jobs/components/job_log.vue
+++ b/app/assets/javascripts/jobs/components/job_log.vue
@@ -1,4 +1,6 @@
<script>
+ import { mapState, mapActions } from 'vuex';
+
export default {
name: 'JobLog',
props: {
@@ -11,10 +13,36 @@
required: true,
},
},
+ computed: {
+ ...mapState(['isScrolledToBottomBeforeReceivingTrace']),
+ },
+ updated() {
+ this.$nextTick(() => this.handleScrollDown());
+ },
+ mounted() {
+ this.$nextTick(() => this.handleScrollDown());
+ },
+ 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 @@
</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 3e62ababea3..94ab1b16c84 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -1,72 +1,73 @@
<script>
- import { polyfillSticky } from '~/lib/utils/sticky';
- import Icon from '~/vue_shared/components/icon.vue';
- import tooltip from '~/vue_shared/directives/tooltip';
- import { numberToHumanSize } from '~/lib/utils/number_utils';
- import { sprintf } from '~/locale';
+import { polyfillSticky } from '~/lib/utils/sticky';
+import Icon from '~/vue_shared/components/icon.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { sprintf } from '~/locale';
+import scrollDown from '../svg/scroll_down.svg';
- export default {
- components: {
- Icon,
+export default {
+ components: {
+ Icon,
+ },
+ directives: {
+ tooltip,
+ },
+ scrollDown,
+ props: {
+ erasePath: {
+ type: String,
+ required: false,
+ default: null,
},
- directives: {
- tooltip,
+ size: {
+ type: Number,
+ required: true,
},
- props: {
- erasePath: {
- type: String,
- required: false,
- default: null,
- },
- size: {
- type: Number,
- required: true,
- },
- rawPath: {
- type: String,
- required: false,
- default: null,
- },
- isScrollTopDisabled: {
- type: Boolean,
- required: true,
- },
- isScrollBottomDisabled: {
- type: Boolean,
- required: true,
- },
- isScrollingDown: {
- type: Boolean,
- required: true,
- },
- isTraceSizeVisible: {
- type: Boolean,
- required: true,
- },
+ rawPath: {
+ type: String,
+ required: false,
+ default: null,
},
- computed: {
- jobLogSize() {
- return sprintf('Showing last %{size} of log -', {
- size: numberToHumanSize(this.size),
- });
- },
+ isScrollTopDisabled: {
+ type: Boolean,
+ required: true,
},
- mounted() {
- polyfillSticky(this.$el);
+ isScrollBottomDisabled: {
+ type: Boolean,
+ required: true,
},
- methods: {
- handleScrollToTop() {
- this.$emit('scrollJobLogTop');
- },
- handleScrollToBottom() {
- this.$emit('scrollJobLogBottom');
- },
+ isScrollingDown: {
+ type: Boolean,
+ required: true,
},
-
- };
+ isTraceSizeVisible: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ jobLogSize() {
+ return sprintf('Showing last %{size} of log -', {
+ size: numberToHumanSize(this.size),
+ });
+ },
+ },
+ mounted() {
+ polyfillSticky(this.$el);
+ },
+ methods: {
+ handleScrollToTop() {
+ this.$emit('scrollJobLogTop');
+ },
+ handleScrollToBottom() {
+ this.$emit('scrollJobLogBottom');
+ },
+ },
+};
</script>
<template>
- <div class="top-bar">
+ <div class="top-bar affix">
<!-- truncate information -->
<div class="js-truncated-info truncated-info d-none d-sm-block float-left">
<template v-if="isTraceSizeVisible">
@@ -101,7 +102,7 @@
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"
@@ -139,8 +140,8 @@
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 271b7790d75..951bcb36600 100644
--- a/app/assets/javascripts/jobs/components/jobs_container.vue
+++ b/app/assets/javascripts/jobs/components/jobs_container.vue
@@ -1,74 +1,35 @@
<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,
+export default {
+ components: {
+ JobContainerItem,
+ },
+
+ props: {
+ jobs: {
+ type: Array,
+ required: true,
},
- props: {
- jobs: {
- type: Array,
- required: true,
- },
- jobId: {
- type: Number,
- required: true,
- },
+ jobId: {
+ type: Number,
+ required: true,
},
- methods: {
- isJobActive(currentJobId) {
- return this.jobId === currentJobId;
- },
- tooltipText(job) {
- return `${_.escape(job.name)} - ${job.status.tooltip}`;
- },
+ },
+ methods: {
+ isJobActive(currentJobId) {
+ return this.jobId === currentJobId;
},
- };
+ },
+};
</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 22bcd402e72..906769ee6a2 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -1,294 +1,284 @@
<script>
- import _ from 'underscore';
- import { mapActions, mapState } from 'vuex';
- import timeagoMixin from '~/vue_shared/mixins/timeago';
- import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
- import Icon from '~/vue_shared/components/icon.vue';
- import DetailRow from './sidebar_detail_row.vue';
- import ArtifactsBlock from './artifacts_block.vue';
- import TriggerBlock from './trigger_block.vue';
- import CommitBlock from './commit_block.vue';
- import StagesDropdown from './stages_dropdown.vue';
- import JobsContainer from './jobs_container.vue';
+import _ from 'underscore';
+import { mapActions, mapState } from 'vuex';
+import timeagoMixin from '~/vue_shared/mixins/timeago';
+import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
+import Icon from '~/vue_shared/components/icon.vue';
+import DetailRow from './sidebar_detail_row.vue';
+import ArtifactsBlock from './artifacts_block.vue';
+import TriggerBlock from './trigger_block.vue';
+import CommitBlock from './commit_block.vue';
+import StagesDropdown from './stages_dropdown.vue';
+import JobsContainer from './jobs_container.vue';
- export default {
- name: 'JobSidebar',
- components: {
- ArtifactsBlock,
- CommitBlock,
- DetailRow,
- Icon,
- TriggerBlock,
- StagesDropdown,
- JobsContainer,
+export default {
+ name: 'JobSidebar',
+ components: {
+ ArtifactsBlock,
+ CommitBlock,
+ DetailRow,
+ Icon,
+ TriggerBlock,
+ StagesDropdown,
+ JobsContainer,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ runnerHelpUrl: {
+ type: String,
+ required: false,
+ default: '',
},
- mixins: [timeagoMixin],
- props: {
- runnerHelpUrl: {
- type: String,
- required: false,
- default: '',
- },
- terminalPath: {
- type: String,
- required: false,
- default: null,
- },
+ },
+ computed: {
+ ...mapState(['job', 'stages', 'jobs', 'selectedStage']),
+ coverage() {
+ return `${this.job.coverage}%`;
},
- computed: {
- ...mapState(['job', 'isLoading', 'stages', 'jobs']),
- coverage() {
- return `${this.job.coverage}%`;
- },
- duration() {
- return timeIntervalInWords(this.job.duration);
- },
- queued() {
- return timeIntervalInWords(this.job.queued);
- },
- runnerId() {
- return `${this.job.runner.description} (#${this.job.runner.id})`;
- },
- retryButtonClass() {
- let className =
- 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
- className +=
- this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
- return className;
- },
- hasTimeout() {
- return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
- },
- timeout() {
- if (this.job.metadata == null) {
- return '';
- }
+ duration() {
+ return timeIntervalInWords(this.job.duration);
+ },
+ queued() {
+ return timeIntervalInWords(this.job.queued);
+ },
+ runnerId() {
+ return `${this.job.runner.description} (#${this.job.runner.id})`;
+ },
+ retryButtonClass() {
+ let className =
+ 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
+ className +=
+ this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
+ return className;
+ },
+ hasTimeout() {
+ return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
+ },
+ timeout() {
+ if (this.job.metadata == null) {
+ return '';
+ }
- let t = this.job.metadata.timeout_human_readable;
- if (this.job.metadata.timeout_source !== '') {
- t += ` (from ${this.job.metadata.timeout_source})`;
- }
+ let t = this.job.metadata.timeout_human_readable;
+ if (this.job.metadata.timeout_source !== '') {
+ t += ` (from ${this.job.metadata.timeout_source})`;
+ }
- return t;
- },
- renderBlock() {
- return (
- this.job.merge_request ||
- this.job.duration ||
- this.job.finished_data ||
- this.job.erased_at ||
- this.job.queued ||
- this.job.runner ||
- this.job.coverage ||
- this.job.tags.length ||
- this.job.cancel_path
- );
- },
- hasArtifact() {
- return !_.isEmpty(this.job.artifact);
- },
- hasTriggers() {
- return !_.isEmpty(this.job.trigger);
- },
- hasStages() {
- return (
- (this.job &&
- this.job.pipeline &&
- this.job.pipeline.stages &&
- this.job.pipeline.stages.length > 0) ||
- false
- );
- },
- commit() {
- return this.job.pipeline.commit || {};
- },
+ return t;
+ },
+ renderBlock() {
+ return (
+ this.job.merge_request ||
+ this.job.duration ||
+ this.job.finished_data ||
+ this.job.erased_at ||
+ this.job.queued ||
+ this.job.runner ||
+ this.job.coverage ||
+ this.job.tags.length ||
+ this.job.cancel_path
+ );
+ },
+ hasArtifact() {
+ return !_.isEmpty(this.job.artifact);
},
- methods: {
- ...mapActions(['fetchJobsForStage']),
+ hasTriggers() {
+ return !_.isEmpty(this.job.trigger);
},
- };
+ hasStages() {
+ return (
+ (this.job &&
+ this.job.pipeline &&
+ this.job.pipeline.stages &&
+ this.job.pipeline.stages.length > 0) ||
+ false
+ );
+ },
+ commit() {
+ return this.job.pipeline && this.job.pipeline.commit ? this.job.pipeline.commit : {};
+ },
+ },
+ methods: {
+ ...mapActions(['fetchJobsForStage', 'toggleSidebar']),
+ },
+};
</script>
<template>
<aside
- class="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"
- @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/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
index 83560a8ff0e..aeafe98a70b 100644
--- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue
@@ -1,31 +1,31 @@
<script>
- export default {
- name: 'SidebarDetailRow',
- props: {
- title: {
- type: String,
- required: false,
- default: '',
- },
- value: {
- type: String,
- required: true,
- },
- helpUrl: {
- type: String,
- required: false,
- default: '',
- },
+export default {
+ name: 'SidebarDetailRow',
+ props: {
+ title: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- hasTitle() {
- return this.title.length > 0;
- },
- hasHelpURL() {
- return this.helpUrl.length > 0;
- },
+ value: {
+ type: String,
+ required: true,
},
- };
+ helpUrl: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ computed: {
+ hasTitle() {
+ return this.title.length > 0;
+ },
+ hasHelpURL() {
+ return this.helpUrl.length > 0;
+ },
+ },
+};
</script>
<template>
<p class="build-detail-row">
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index 1c15af55a8b..e5e1d56e287 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -1,50 +1,39 @@
<script>
- import _ from 'underscore';
- import CiIcon from '~/vue_shared/components/ci_icon.vue';
- import Icon from '~/vue_shared/components/icon.vue';
- import { __ } from '~/locale';
+import _ from 'underscore';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
- export default {
- components: {
- CiIcon,
- Icon,
+export default {
+ components: {
+ CiIcon,
+ Icon,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: true,
},
- props: {
- pipeline: {
- type: Object,
- required: true,
- },
- stages: {
- type: Array,
- required: true,
- },
+ stages: {
+ type: Array,
+ required: true,
},
- data() {
- return {
- selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'),
- };
+ selectedStage: {
+ type: String,
+ required: true,
},
- computed: {
- hasRef() {
- return !_.isEmpty(this.pipeline.ref);
- },
- },
- watch: {
- // When the component is initially mounted it may start with an empty stages array.
- // Once the prop is updated, we set the first stage as the selected one
- stages(newVal) {
- if (newVal.length) {
- this.selectedStage = newVal[0].name;
- }
- },
+ },
+
+ computed: {
+ hasRef() {
+ return !_.isEmpty(this.pipeline.ref);
},
- methods: {
- onStageClick(stage) {
- this.$emit('requestSidebarStageDropdown', stage);
- this.selectedStage = stage.name;
- },
+ },
+ methods: {
+ onStageClick(stage) {
+ this.$emit('requestSidebarStageDropdown', stage);
},
- };
+ },
+};
</script>
<template>
<div class="block-last dropdown">
diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue
index d7b3c4fcb5b..41de4a6e85a 100644
--- a/app/assets/javascripts/jobs/components/trigger_block.vue
+++ b/app/assets/javascripts/jobs/components/trigger_block.vue
@@ -1,27 +1,27 @@
<script>
- export default {
- props: {
- trigger: {
- type: Object,
- required: true,
- },
+export default {
+ props: {
+ trigger: {
+ type: Object,
+ required: true,
},
- data() {
- return {
- areVariablesVisible: false,
- };
+ },
+ data() {
+ return {
+ areVariablesVisible: false,
+ };
+ },
+ computed: {
+ hasVariables() {
+ return this.trigger.variables && this.trigger.variables.length > 0;
},
- computed: {
- hasVariables() {
- return this.trigger.variables && this.trigger.variables.length > 0;
- },
+ },
+ methods: {
+ revealVariables() {
+ this.areVariablesVisible = true;
},
- methods: {
- revealVariables() {
- this.areVariablesVisible = true;
- },
- },
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/jobs/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 3eb75e72506..00000000000
--- a/app/assets/javascripts/jobs/job_details_bundle.js
+++ /dev/null
@@ -1,74 +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');
-
- // eslint-disable-next-line no-new
- new Job();
-
- 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,
- runnerHelpUrl: dataset.runnerHelpUrl,
- },
- });
- },
- });
-
- // 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,
- },
- });
- },
- });
-};
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 298367c9342..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
*/
@@ -139,10 +186,12 @@ export const fetchStages = ({ state, dispatch }) => {
dispatch('requestStages');
axios
- .get(state.job.pipeline.path)
+ .get(`${state.job.pipeline.path}.json`)
.then(({ data }) => {
+ // Set selected stage
dispatch('receiveStagesSuccess', data.details.stages);
- dispatch('fetchJobsForStage', data.details.stages[0]);
+ const selectedStage = data.details.stages.find(stage => stage.name === state.selectedStage);
+ dispatch('fetchJobsForStage', selectedStage);
})
.catch(() => dispatch('receiveStagesError'));
};
@@ -156,11 +205,12 @@ export const receiveStagesError = ({ commit }) => {
/**
* Jobs list on sidebar - depend on stages dropdown
*/
-export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE);
+export const requestJobsForStage = ({ commit }, stage) =>
+ commit(types.REQUEST_JOBS_FOR_STAGE, stage);
// On stage click, set selected stage + fetch job
export const fetchJobsForStage = ({ dispatch }, stage) => {
- dispatch('requestJobsForStage');
+ dispatch('requestJobsForStage', stage);
axios
.get(stage.dropdown_path, {
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index afe5f88b292..4ce395a9106 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) {
@@ -22,10 +23,10 @@ export const shouldRenderCalloutMessage = state =>
!_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message);
/**
- * When job has not started the key will be `false`
+ * When job has not started the key will be null
* When job started the key will be a string with a date.
*/
-export const jobHasStarted = state => !(state.job.started === false);
+export const shouldRenderTriggeredLabel = state => _.isString(state.job.started);
export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
@@ -34,11 +35,12 @@ 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) || {};
+export const emptyStateAction = state => (state.job && state.job.status && state.job.status.action) || {};
/**
* When the job is pending and there are no available runners
* we need to render the stuck block;
@@ -46,8 +48,10 @@ export const emptyStateIllustration = state =>
* @returns {Boolean}
*/
export const isJobStuck = state =>
- state.job.status.group === 'pending' &&
+ (!_.isEmpty(state.job.status) && state.job.status.group === 'pending') &&
(!_.isEmpty(state.job.runners) && state.job.runners.available === false);
+export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceComplete;
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/jobs/store/index.js b/app/assets/javascripts/jobs/store/index.js
index 96e38f9a2fa..bba01426af7 100644
--- a/app/assets/javascripts/jobs/store/index.js
+++ b/app/assets/javascripts/jobs/store/index.js
@@ -7,9 +7,10 @@ import mutations from './mutations';
Vue.use(Vuex);
-export default () => new Vuex.Store({
- actions,
- mutations,
- getters,
- state: state(),
-});
+export default () =>
+ new Vuex.Store({
+ actions,
+ mutations,
+ getters,
+ state: state(),
+ });
diff --git a/app/assets/javascripts/jobs/store/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 c3f2359fa4d..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,40 +40,65 @@ 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;
+
+ /**
+ * We only update it on the first request
+ * The dropdown can be changed by the user
+ * after the first request,
+ * and we do not want to hijack that
+ */
+ if (state.selectedStage === 'More' && job.stage) {
+ state.selectedStage = job.stage;
+ }
},
[types.RECEIVE_JOB_ERROR](state) {
state.isLoading = false;
- 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) {
@@ -81,8 +113,9 @@ export default {
state.stages = [];
},
- [types.REQUEST_JOBS_FOR_STAGE](state) {
+ [types.REQUEST_JOBS_FOR_STAGE](state, stage) {
state.isLoadingJobs = true;
+ state.selectedStage = stage.name;
},
[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](state, jobs) {
state.isLoadingJobs = false;
diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js
index 509cb69a5d3..0eb269ca38f 100644
--- a/app/assets/javascripts/jobs/store/state.js
+++ b/app/assets/javascripts/jobs/store/state.js
@@ -1,40 +1,35 @@
+import { __ } from '~/locale';
+
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,
-
- trace: '',
+ // scroll buttons state
+ isScrollBottomDisabled: true,
+ isScrollTopDisabled: true,
- 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: null,
+ selectedStage: __('More'),
stages: [],
jobs: [],
});
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/labels_select.js b/app/assets/javascripts/labels_select.js
index 68ae1ca6842..3c38d998b6c 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -11,6 +11,7 @@ import DropdownUtils from './filtered_search/dropdown_utils';
import CreateLabelDropdown from './create_label';
import flash from './flash';
import ModalStore from './boards/stores/modal_store';
+import boardsStore from './boards/stores/boards_store';
export default class LabelsSelect {
constructor(els, options = {}) {
@@ -378,7 +379,7 @@ export default class LabelsSelect {
}
else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if ($el.hasClass('is-active')) {
- gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({
+ boardsStore.detail.issue.labels.push(new ListLabel({
id: label.id,
title: label.title,
color: label.color[0],
@@ -386,16 +387,16 @@ export default class LabelsSelect {
}));
}
else {
- var { labels } = gl.issueBoards.BoardsStore.detail.issue;
+ var { labels } = boardsStore.detail.issue;
labels = labels.filter(function (selectedLabel) {
return selectedLabel.id !== label.id;
});
- gl.issueBoards.BoardsStore.detail.issue.labels = labels;
+ boardsStore.detail.issue.labels = labels;
}
$loading.fadeIn();
- gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
+ boardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
.then(fadeOutLoader)
.catch(fadeOutLoader);
}
diff --git a/app/assets/javascripts/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/ace.js b/app/assets/javascripts/lib/ace.js
index 9cdc0309503..e90b3d2eec7 100644
--- a/app/assets/javascripts/lib/ace.js
+++ b/app/assets/javascripts/lib/ace.js
@@ -1,3 +1,4 @@
/*= require ace/ace */
+/*= require ace/ext-modelist */
/*= require ace/ext-searchbox */
/*= require ./ace/ace_config_paths */
diff --git a/app/assets/javascripts/lib/utils/ace_utils.js b/app/assets/javascripts/lib/utils/ace_utils.js
new file mode 100644
index 00000000000..efc4b2a8d94
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/ace_utils.js
@@ -0,0 +1,6 @@
+/* global ace */
+
+export default function getModeByFileExtension(path) {
+ const modelist = ace.require("ace/ext/modelist");
+ return modelist.getModeForPath(path).mode;
+};
diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js
index 616d8952ada..2d976dbdbbe 100644
--- a/app/assets/javascripts/lib/utils/ajax_cache.js
+++ b/app/assets/javascripts/lib/utils/ajax_cache.js
@@ -4,7 +4,7 @@ import Cache from './cache';
class AjaxCache extends Cache {
constructor() {
super();
- this.pendingRequests = { };
+ this.pendingRequests = {};
}
override(endpoint, data) {
@@ -19,12 +19,13 @@ class AjaxCache extends Cache {
let pendingRequest = this.pendingRequests[endpoint];
if (!pendingRequest) {
- pendingRequest = axios.get(endpoint)
+ pendingRequest = axios
+ .get(endpoint)
.then(({ data }) => {
this.internalStorage[endpoint] = data;
delete this.pendingRequests[endpoint];
})
- .catch((e) => {
+ .catch(e => {
const error = new Error(`${endpoint}: ${e.message}`);
error.textStatus = e.message;
diff --git a/app/assets/javascripts/lib/utils/axios_utils.js b/app/assets/javascripts/lib/utils/axios_utils.js
index 792871e2ecf..69159e2d741 100644
--- a/app/assets/javascripts/lib/utils/axios_utils.js
+++ b/app/assets/javascripts/lib/utils/axios_utils.js
@@ -7,7 +7,7 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// Maintain a global counter for active requests
// see: spec/support/wait_for_requests.rb
-axios.interceptors.request.use((config) => {
+axios.interceptors.request.use(config => {
window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1;
@@ -15,15 +15,18 @@ axios.interceptors.request.use((config) => {
});
// Remove the global counter
-axios.interceptors.response.use((config) => {
- window.activeVueResources -= 1;
-
- return config;
-}, (e) => {
- window.activeVueResources -= 1;
-
- return Promise.reject(e);
-});
+axios.interceptors.response.use(
+ config => {
+ window.activeVueResources -= 1;
+
+ return config;
+ },
+ e => {
+ window.activeVueResources -= 1;
+
+ return Promise.reject(e);
+ },
+);
export default axios;
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
index c28ed04f94f..a24c71aeab1 100644
--- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
@@ -93,9 +93,13 @@ export default class LinkedTabs {
const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
- window.history.replaceState({
- url: newState,
- }, document.title, newState);
+ window.history.replaceState(
+ {
+ url: newState,
+ },
+ document.title,
+ newState,
+ );
return newState;
}
diff --git a/app/assets/javascripts/lib/utils/cache.js b/app/assets/javascripts/lib/utils/cache.js
index 596bd1e388a..45048145139 100644
--- a/app/assets/javascripts/lib/utils/cache.js
+++ b/app/assets/javascripts/lib/utils/cache.js
@@ -1,6 +1,6 @@
export default class Cache {
constructor() {
- this.internalStorage = { };
+ this.internalStorage = {};
}
get(key) {
diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js
index e98c9068367..19e4085dbbb 100644
--- a/app/assets/javascripts/lib/utils/datefix.js
+++ b/app/assets/javascripts/lib/utils/datefix.js
@@ -1,12 +1,11 @@
-
-export const pad = (val, len = 2) => (`0${val}`).slice(-len);
+export const pad = (val, len = 2) => `0${val}`.slice(-len);
/**
* Formats dates in Pickaday
* @param {String} dateString Date in yyyy-mm-dd format
* @return {Date} UTC format
*/
-export const parsePikadayDate = (dateString) => {
+export const parsePikadayDate = dateString => {
const parts = dateString.split('-');
const year = parseInt(parts[0], 10);
const month = parseInt(parts[1] - 1, 10);
@@ -20,7 +19,7 @@ export const parsePikadayDate = (dateString) => {
* @param {Date} date UTC format
* @return {String} Date formated in yyyy-mm-dd
*/
-export const pikadayToString = (date) => {
+export const pikadayToString = date => {
const day = pad(date.getDate());
const month = pad(date.getMonth() + 1);
const year = date.getFullYear();
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index f4eb652a41a..d93873e0214 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -8,7 +8,7 @@ function notificationGranted(message, opts, onclick) {
return notification.close();
}, 8000);
- return notification.onclick = onclick || notification.close;
+ return (notification.onclick = onclick || notification.close);
}
function notifyPermissions() {
@@ -21,7 +21,7 @@ function notifyMe(message, body, icon, onclick) {
var opts;
opts = {
body: body,
- icon: icon
+ icon: icon,
};
// Let's check if the browser supports notifications
if (!('Notification' in window)) {
diff --git a/app/assets/javascripts/lib/utils/pretty_time.js b/app/assets/javascripts/lib/utils/pretty_time.js
index b1ffd797f7e..d92b8a7179f 100644
--- a/app/assets/javascripts/lib/utils/pretty_time.js
+++ b/app/assets/javascripts/lib/utils/pretty_time.js
@@ -27,10 +27,10 @@ export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {})
let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR);
- return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => {
+ return _.mapObject(timePeriodConstraints, minutesPerPeriod => {
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
- unorderedMinutes -= (periodCount * minutesPerPeriod);
+ unorderedMinutes -= periodCount * minutesPerPeriod;
return periodCount;
});
@@ -42,10 +42,14 @@ export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {})
*/
export function stringifyTime(timeObject) {
- const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => {
- const isNonZero = !!unitValue;
- return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
- }, '').trim();
+ const reducedTime = _.reduce(
+ timeObject,
+ (memo, unitValue, unitName) => {
+ const isNonZero = !!unitValue;
+ return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
+ },
+ '',
+ ).trim();
return reducedTime.length ? reducedTime : '0m';
}
@@ -55,7 +59,5 @@ export function stringifyTime(timeObject) {
*/
export function abbreviateTime(timeStr) {
- return timeStr.split(' ')
- .filter(unitStr => unitStr.charAt(0) !== '0')[0];
+ return timeStr.split(' ').filter(unitStr => unitStr.charAt(0) !== '0')[0];
}
-
diff --git a/app/assets/javascripts/lib/utils/regexp.js b/app/assets/javascripts/lib/utils/regexp.js
index baa0b51d59b..25b60dcd14a 100644
--- a/app/assets/javascripts/lib/utils/regexp.js
+++ b/app/assets/javascripts/lib/utils/regexp.js
@@ -5,6 +5,7 @@
// Inspired by https://github.com/mishoo/UglifyJS/blob/2bc1d02363db3798d5df41fb5059a19edca9b7eb/lib/parse-js.js#L203
// Unicode 6.1
-const unicodeLetters = '\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC';
+const unicodeLetters =
+ '\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u0527\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0\\u08A2-\\u08AC\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0977\\u0979-\\u097F\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F0\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191C\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA697\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7F8-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA80-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC';
export default { unicodeLetters };
diff --git a/app/assets/javascripts/lib/utils/simple_poll.js b/app/assets/javascripts/lib/utils/simple_poll.js
index 25ca98afbe7..473f179ad86 100644
--- a/app/assets/javascripts/lib/utils/simple_poll.js
+++ b/app/assets/javascripts/lib/utils/simple_poll.js
@@ -2,7 +2,7 @@ export default (fn, interval = 2000, timeout = 60000) => {
const startTime = Date.now();
return new Promise((resolve, reject) => {
- const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg));
+ const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
const next = () => {
if (Date.now() - startTime < timeout) {
setTimeout(fn.bind(null, next, stop), interval);
diff --git a/app/assets/javascripts/lib/utils/sticky.js b/app/assets/javascripts/lib/utils/sticky.js
index 15a4dd62012..f3244301350 100644
--- a/app/assets/javascripts/lib/utils/sticky.js
+++ b/app/assets/javascripts/lib/utils/sticky.js
@@ -24,7 +24,11 @@ export const isSticky = (el, scrollY, stickyTop, insertPlaceholder) => {
} else if (top > stickyTop && el.classList.contains('is-stuck')) {
el.classList.remove('is-stuck');
- if (insertPlaceholder && el.nextElementSibling && el.nextElementSibling.classList.contains('sticky-placeholder')) {
+ if (
+ insertPlaceholder &&
+ el.nextElementSibling &&
+ el.nextElementSibling.classList.contains('sticky-placeholder')
+ ) {
el.nextElementSibling.remove();
}
}
@@ -42,11 +46,19 @@ export const isSticky = (el, scrollY, stickyTop, insertPlaceholder) => {
export const stickyMonitor = (el, stickyTop, insertPlaceholder = true) => {
if (!el) return;
- if (typeof CSS === 'undefined' || !(CSS.supports('(position: -webkit-sticky) or (position: sticky)'))) return;
+ if (
+ typeof CSS === 'undefined' ||
+ !CSS.supports('(position: -webkit-sticky) or (position: sticky)')
+ )
+ return;
- document.addEventListener('scroll', () => isSticky(el, window.scrollY, stickyTop, insertPlaceholder), {
- passive: true,
- });
+ document.addEventListener(
+ 'scroll',
+ () => isSticky(el, window.scrollY, stickyTop, insertPlaceholder),
+ {
+ passive: true,
+ },
+ );
};
/**
@@ -55,6 +67,6 @@ export const stickyMonitor = (el, stickyTop, insertPlaceholder = true) => {
* - If the current environment supports `position: sticky`, do nothing.
* - Can receive an iterable element list (NodeList, jQuery collection, etc.) or single HTMLElement.
*/
-export const polyfillSticky = (el) => {
+export const polyfillSticky = el => {
StickyFill.add(el);
};
diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js
index df20785b178..e26a6b986be 100644
--- a/app/assets/javascripts/lib/utils/text_markdown.js
+++ b/app/assets/javascripts/lib/utils/text_markdown.js
@@ -8,12 +8,18 @@ function selectedText(text, textarea) {
function lineBefore(text, textarea) {
var split;
- split = text.substring(0, textarea.selectionStart).trim().split('\n');
+ split = text
+ .substring(0, textarea.selectionStart)
+ .trim()
+ .split('\n');
return split[split.length - 1];
}
function lineAfter(text, textarea) {
- return text.substring(textarea.selectionEnd).trim().split('\n')[0];
+ return text
+ .substring(textarea.selectionEnd)
+ .trim()
+ .split('\n')[0];
}
function blockTagText(text, textArea, blockTag, selected) {
@@ -27,7 +33,7 @@ function blockTagText(text, textArea, blockTag, selected) {
}
return selected;
} else {
- return blockTag + "\n" + selected + "\n" + blockTag;
+ return blockTag + '\n' + selected + '\n' + blockTag;
}
}
@@ -58,7 +64,14 @@ function moveCursor({ textArea, tag, wrapped, removedLastNewLine, select }) {
}
export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wrap, select }) {
- var textToInsert, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine, currentLineEmpty, lastNewLine;
+ var textToInsert,
+ inserted,
+ selectedSplit,
+ startChar,
+ removedLastNewLine,
+ removedFirstNewLine,
+ currentLineEmpty,
+ lastNewLine;
removedLastNewLine = false;
removedFirstNewLine = false;
currentLineEmpty = false;
@@ -94,21 +107,23 @@ export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wr
if (blockTag != null && blockTag !== '') {
textToInsert = blockTagText(text, textArea, blockTag, selected);
} else {
- textToInsert = selectedSplit.map(function(val) {
- if (tag.indexOf(textPlaceholder) > -1) {
- return tag.replace(textPlaceholder, val);
- }
- if (val.indexOf(tag) === 0) {
- return "" + (val.replace(tag, ''));
- } else {
- return "" + tag + val;
- }
- }).join('\n');
+ textToInsert = selectedSplit
+ .map(function(val) {
+ if (tag.indexOf(textPlaceholder) > -1) {
+ return tag.replace(textPlaceholder, val);
+ }
+ if (val.indexOf(tag) === 0) {
+ return '' + val.replace(tag, '');
+ } else {
+ return '' + tag + val;
+ }
+ })
+ .join('\n');
}
} else if (tag.indexOf(textPlaceholder) > -1) {
textToInsert = tag.replace(textPlaceholder, selected);
} else {
- textToInsert = "" + startChar + tag + selected + (wrap ? tag : ' ');
+ textToInsert = '' + startChar + tag + selected + (wrap ? tag : ' ');
}
if (removedFirstNewLine) {
@@ -120,7 +135,13 @@ export function insertMarkdownText({ textArea, text, tag, blockTag, selected, wr
}
insertText(textArea, textToInsert);
- return moveCursor({ textArea, tag: tag.replace(textPlaceholder, selected), wrap, removedLastNewLine, select });
+ return moveCursor({
+ textArea,
+ tag: tag.replace(textPlaceholder, selected),
+ wrap,
+ removedLastNewLine,
+ select,
+ });
}
function updateText({ textArea, tag, blockTag, wrap, select }) {
@@ -138,15 +159,18 @@ function replaceRange(s, start, end, substitute) {
}
export function addMarkdownListeners(form) {
- return $('.js-md', form).off('click').on('click', function() {
- const $this = $(this);
- return updateText({
- textArea: $this.closest('.md-area').find('textarea'),
- tag: $this.data('mdTag'),
- blockTag: $this.data('mdBlock'),
- wrap: !$this.data('mdPrepend'),
- select: $this.data('mdSelect') });
- });
+ return $('.js-md', form)
+ .off('click')
+ .on('click', function() {
+ const $this = $(this);
+ return updateText({
+ textArea: $this.closest('.md-area').find('textarea'),
+ tag: $this.data('mdTag'),
+ blockTag: $this.data('mdBlock'),
+ wrap: !$this.data('mdPrepend'),
+ select: $this.data('mdSelect'),
+ });
+ });
}
export function removeMarkdownListeners(form) {
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index 879f94a26ec..7cc7cd6d20e 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -8,7 +8,7 @@
* @returns {String}
*/
export const addDelimiter = text =>
- (text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text);
+ text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') : text;
/**
* Returns '99+' for numbers bigger than 99.
@@ -53,10 +53,7 @@ export const slugify = str => str.trim().toLowerCase();
* @param {String} str
* @returns {String}
*/
-export const slugifyWithHyphens = str => {
- const regex = new RegExp(/\s+/, 'g');
- return str.toLowerCase().replace(regex, '-');
-};
+export const slugifyWithHyphens = str => str.toLowerCase().replace(/\s+/g, '-');
/**
* Truncates given text
@@ -94,9 +91,7 @@ export function capitalizeFirstCharacter(text) {
* @return {String}
*/
export function getFirstCharacterCapitalized(text) {
- return text
- ? text.charAt(0).toUpperCase()
- : '';
+ return text ? text.charAt(0).toUpperCase() : '';
}
/**
@@ -136,10 +131,9 @@ export const convertToSentenceCase = string => {
* e.g. HelloWorld => Hello World
*
* @param {*} string
-*/
-export const splitCamelCase = string => (
+ */
+export const splitCamelCase = string =>
string
- .replace(/([A-Z]+)([A-Z][a-z])/g, ' $1 $2')
- .replace(/([a-z\d])([A-Z])/g, '$1 $2')
- .trim()
-);
+ .replace(/([A-Z]+)([A-Z][a-z])/g, ' $1 $2')
+ .replace(/([a-z\d])([A-Z])/g, '$1 $2')
+ .trim();
diff --git a/app/assets/javascripts/lib/utils/tick_formats.js b/app/assets/javascripts/lib/utils/tick_formats.js
index 0c10a85e336..af3ca714400 100644
--- a/app/assets/javascripts/lib/utils/tick_formats.js
+++ b/app/assets/javascripts/lib/utils/tick_formats.js
@@ -26,7 +26,7 @@ initDateFormats();
see also https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#tickFormat
*/
-export const dateTickFormat = (date) => {
+export const dateTickFormat = date => {
if (date.getDate() !== 1) {
return dateTimeFormats.dayFormat.format(date);
}
diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js
index b01ec6b81a3..c0d45e017b4 100644
--- a/app/assets/javascripts/lib/utils/users_cache.js
+++ b/app/assets/javascripts/lib/utils/users_cache.js
@@ -7,21 +7,20 @@ class UsersCache extends Cache {
return Promise.resolve(this.get(username));
}
- return Api.users('', { username })
- .then(({ data }) => {
- if (!data.length) {
- throw new Error(`User "${username}" could not be found!`);
- }
+ return Api.users('', { username }).then(({ data }) => {
+ if (!data.length) {
+ throw new Error(`User "${username}" could not be found!`);
+ }
- if (data.length > 1) {
- throw new Error(`Expected username "${username}" to be unique!`);
- }
+ if (data.length > 1) {
+ throw new Error(`Expected username "${username}" to be unique!`);
+ }
- const user = data[0];
- this.internalStorage[username] = user;
- return user;
- });
- // missing catch is intentional, error handling depends on use case
+ const user = data[0];
+ this.internalStorage[username] = user;
+ return user;
+ });
+ // missing catch is intentional, error handling depends on use case
}
}
diff --git a/app/assets/javascripts/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/main.js b/app/assets/javascripts/main.js
index e8aac51a299..a88b575ad99 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -202,7 +202,6 @@ document.addEventListener('DOMContentLoaded', () => {
$('.navbar-toggler').on('click', () => {
$('.header-content').toggleClass('menu-expanded');
- gl.lazyLoader.loadCheck();
});
// Show/hide comments on diff
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index d27922a2099..df5cd1b8c51 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -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_conflicts/components/diff_file_editor.js b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
index c1832d034ef..a62ebe23646 100644
--- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
+++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
@@ -5,8 +5,9 @@ import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
+import getModeByFileExtension from '~/lib/utils/ace_utils';
-((global) => {
+(global => {
global.mergeConflicts = global.mergeConflicts || {};
global.mergeConflicts.diffFileEditor = Vue.extend({
@@ -35,10 +36,10 @@ import { __ } from '~/locale';
computed: {
classObject() {
return {
- 'saved': this.saved,
- 'is-loading': this.loading
+ saved: this.saved,
+ 'is-loading': this.loading,
};
- }
+ },
},
watch: {
['file.showEditor'](val) {
@@ -49,7 +50,7 @@ import { __ } from '~/locale';
}
this.loadEditor();
- }
+ },
},
mounted() {
if (this.file.loadEditor) {
@@ -60,7 +61,8 @@ import { __ } from '~/locale';
loadEditor() {
this.loading = true;
- axios.get(this.file.content_path)
+ axios
+ .get(this.file.content_path)
.then(({ data }) => {
const content = this.$el.querySelector('pre');
const fileContent = document.createTextNode(data.content);
@@ -71,7 +73,7 @@ import { __ } from '~/locale';
this.fileLoaded = true;
this.editor = ace.edit(content);
this.editor.$blockScrolling = Infinity; // Turn off annoying warning
- this.editor.getSession().setMode(`ace/mode/${data.blob_ace_mode}`);
+ this.editor.getSession().setMode(getModeByFileExtension(data.new_path));
this.editor.on('change', () => {
this.saveDiffResolution();
});
@@ -101,7 +103,7 @@ import { __ } from '~/locale';
},
acceptDiscardConfirmation(file) {
this.onAcceptDiscardConfirmation(file);
- }
- }
+ },
+ },
});
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
index 69208ac2d36..c2de0379d23 100644
--- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
@@ -4,7 +4,7 @@ import Vue from 'vue';
import actionsMixin from '../mixins/line_conflict_actions';
import utilsMixin from '../mixins/line_conflict_utils';
-((global) => {
+(global => {
global.mergeConflicts = global.mergeConflicts || {};
global.mergeConflicts.parallelConflictLines = Vue.extend({
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index 2cd70247bc6..0333335de06 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
@@ -4,7 +4,7 @@ import $ from 'jquery';
import Vue from 'vue';
import Cookies from 'js-cookie';
-((global) => {
+(global => {
global.mergeConflicts = global.mergeConflicts || {};
const diffViewType = Cookies.get('diff_view');
@@ -17,11 +17,11 @@ import Cookies from 'js-cookie';
const DEFAULT_RESOLVE_MODE = INTERACTIVE_RESOLVE_MODE;
const VIEW_TYPES = {
INLINE: 'inline',
- PARALLEL: 'parallel'
+ PARALLEL: 'parallel',
};
const CONFLICT_TYPES = {
TEXT: 'text',
- TEXT_EDITOR: 'text-editor'
+ TEXT_EDITOR: 'text-editor',
};
global.mergeConflicts.mergeConflictsStore = {
@@ -31,7 +31,7 @@ import Cookies from 'js-cookie';
isSubmitting: false,
isParallel: diffViewType === VIEW_TYPES.PARALLEL,
diffViewType: diffViewType,
- conflictsData: {}
+ conflictsData: {},
},
setConflictsData(data) {
@@ -47,7 +47,7 @@ import Cookies from 'js-cookie';
},
decorateFiles(files) {
- files.forEach((file) => {
+ files.forEach(file => {
file.content = '';
file.resolutionData = {};
file.promptDiscardConfirmation = false;
@@ -72,7 +72,7 @@ import Cookies from 'js-cookie';
setInlineLine(file) {
file.inlineLines = [];
- file.sections.forEach((section) => {
+ file.sections.forEach(section => {
let currentLineType = 'new';
const { conflict, lines, id } = section;
@@ -80,7 +80,7 @@ import Cookies from 'js-cookie';
file.inlineLines.push(this.getHeadHeaderLine(id));
}
- lines.forEach((line) => {
+ lines.forEach(line => {
const { type } = line;
if ((type === 'new' || type === 'old') && currentLineType !== type) {
@@ -102,7 +102,7 @@ import Cookies from 'js-cookie';
file.parallelLines = [];
const linesObj = { left: [], right: [] };
- file.sections.forEach((section) => {
+ file.sections.forEach(section => {
const { conflict, lines, id } = section;
if (conflict) {
@@ -110,7 +110,7 @@ import Cookies from 'js-cookie';
linesObj.right.push(this.getHeadHeaderLine(id));
}
- lines.forEach((line) => {
+ lines.forEach(line => {
const { type } = line;
if (conflict) {
@@ -131,10 +131,7 @@ import Cookies from 'js-cookie';
});
for (let i = 0, len = linesObj.left.length; i < len; i += 1) {
- file.parallelLines.push([
- linesObj.right[i],
- linesObj.left[i]
- ]);
+ file.parallelLines.push([linesObj.right[i], linesObj.left[i]]);
}
},
@@ -159,9 +156,9 @@ import Cookies from 'js-cookie';
const { files } = this.state.conflictsData;
let count = 0;
- files.forEach((file) => {
+ files.forEach(file => {
if (file.type === CONFLICT_TYPES.TEXT) {
- file.sections.forEach((section) => {
+ file.sections.forEach(section => {
if (section.conflict) {
count += 1;
}
@@ -198,7 +195,7 @@ import Cookies from 'js-cookie';
isHeader: true,
isHead: true,
isSelected: false,
- isUnselected: false
+ isUnselected: false,
};
},
@@ -229,7 +226,7 @@ import Cookies from 'js-cookie';
section: isHead ? 'head' : 'origin',
richText: rich_text,
isSelected: false,
- isUnselected: false
+ isUnselected: false,
};
},
@@ -243,7 +240,7 @@ import Cookies from 'js-cookie';
isHeader: true,
isOrigin: true,
isSelected: false,
- isUnselected: false
+ isUnselected: false,
};
},
@@ -290,14 +287,14 @@ import Cookies from 'js-cookie';
},
restoreFileLinesState(file) {
- file.inlineLines.forEach((line) => {
+ file.inlineLines.forEach(line => {
if (line.hasConflict || line.isHeader) {
line.isSelected = false;
line.isUnselected = false;
}
});
- file.parallelLines.forEach((lines) => {
+ file.parallelLines.forEach(lines => {
const left = lines[0];
const right = lines[1];
const isLeftMatch = left.hasConflict || left.isHeader;
@@ -354,7 +351,7 @@ import Cookies from 'js-cookie';
const initial = 'Commit to source branch';
const inProgress = 'Committing...';
- return this.state ? this.state.isSubmitting ? inProgress : initial : initial;
+ return this.state ? (this.state.isSubmitting ? inProgress : initial) : initial;
},
getCommitData() {
@@ -362,13 +359,13 @@ import Cookies from 'js-cookie';
commitData = {
commit_message: this.state.conflictsData.commitMessage,
- files: []
+ files: [],
};
- this.state.conflictsData.files.forEach((file) => {
+ this.state.conflictsData.files.forEach(file => {
const addFile = {
old_path: file.old_path,
- new_path: file.new_path
+ new_path: file.new_path,
};
if (file.type === CONFLICT_TYPES.TEXT) {
@@ -391,13 +388,13 @@ import Cookies from 'js-cookie';
handleSelected(file, sectionId, selection) {
Vue.set(file.resolutionData, sectionId, selection);
- file.inlineLines.forEach((line) => {
+ file.inlineLines.forEach(line => {
if (line.id === sectionId && (line.hasConflict || line.isHeader)) {
this.markLine(line, selection);
}
});
- file.parallelLines.forEach((lines) => {
+ file.parallelLines.forEach(lines => {
const left = lines[0];
const right = lines[1];
const hasSameId = right.id === sectionId || left.id === sectionId;
@@ -430,6 +427,6 @@ import Cookies from 'js-cookie';
fileTextTypePresent() {
return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT);
- }
+ },
};
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/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/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 67c2d7909a2..42fb5c7177a 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -9,6 +9,7 @@ import '~/gl_dropdown';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
import ModalStore from './boards/stores/modal_store';
+import boardsStore, { boardStoreIssueSet, boardStoreIssueDelete } from './boards/stores/boards_store';
export default class MilestoneSelect {
constructor(currentProject, els, options = {}) {
@@ -187,7 +188,7 @@ export default class MilestoneSelect {
return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (selected.id !== -1 && isSelecting) {
- gl.issueBoards.boardStoreIssueSet(
+ boardStoreIssueSet(
'milestone',
new ListMilestone({
id: selected.id,
@@ -195,13 +196,13 @@ export default class MilestoneSelect {
}),
);
} else {
- gl.issueBoards.boardStoreIssueDelete('milestone');
+ boardStoreIssueDelete('milestone');
}
$dropdown.trigger('loading.gl.dropdown');
$loading.removeClass('hidden').fadeIn();
- gl.issueBoards.BoardsStore.detail.issue
+ boardsStore.detail.issue
.update($dropdown.attr('data-issue-update'))
.then(() => {
$dropdown.trigger('loaded.gl.dropdown');
diff --git a/app/assets/javascripts/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 3cccaf72ed7..ed5c8b15945 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -148,7 +148,7 @@ export default {
point = point.matrixTransform(this.$refs.graphData.getScreenCTM().inverse());
point.x += 7;
- this.seriesUnderMouse = this.timeSeries.filter((series) => {
+ this.seriesUnderMouse = this.timeSeries.filter(series => {
const mouseX = series.timeSeriesScaleX.invert(point.x);
let minDistance = Infinity;
@@ -221,21 +221,18 @@ export default {
.scale(axisYScale)
.ticks(measurements.yTicks);
- d3
- .select(this.$refs.baseSvg)
+ d3.select(this.$refs.baseSvg)
.select('.x-axis')
.call(xAxis);
const width = this.graphWidth;
- d3
- .select(this.$refs.baseSvg)
+ d3.select(this.$refs.baseSvg)
.select('.y-axis')
.call(yAxis)
.selectAll('.tick')
.each(function createTickLines(d, i) {
if (i > 0) {
- d3
- .select(this)
+ d3.select(this)
.select('line')
.attr('x2', width)
.attr('class', 'axis-tick');
diff --git a/app/assets/javascripts/monitoring/components/graph/axis.vue b/app/assets/javascripts/monitoring/components/graph/axis.vue
index 8a604a51eb2..616410ec34f 100644
--- a/app/assets/javascripts/monitoring/components/graph/axis.vue
+++ b/app/assets/javascripts/monitoring/components/graph/axis.vue
@@ -38,38 +38,25 @@ export default {
computed: {
textTransform() {
const yCoordinate =
- (this.graphHeight -
- this.margin.top +
- this.measurements.axisLabelLineOffset) /
- 2 || 0;
+ (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 || 0;
return `translate(15, ${yCoordinate}) rotate(-90)`;
},
rectTransform() {
const yCoordinate =
- (this.graphHeight -
- this.margin.top +
- this.measurements.axisLabelLineOffset) /
- 2 +
+ (this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset) / 2 +
this.yLabelWidth / 2 || 0;
return `translate(0, ${yCoordinate}) rotate(-90)`;
},
xPosition() {
- return (
- (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 -
- this.margin.right || 0
- );
+ return (this.graphWidth + this.measurements.axisLabelLineOffset) / 2 - this.margin.right || 0;
},
yPosition() {
- return (
- this.graphHeight -
- this.margin.top +
- this.measurements.axisLabelLineOffset || 0
- );
+ return this.graphHeight - this.margin.top + this.measurements.axisLabelLineOffset || 0;
},
yAxisLabelSentenceCase() {
diff --git a/app/assets/javascripts/monitoring/components/graph/flag.vue b/app/assets/javascripts/monitoring/components/graph/flag.vue
index 5f00d20ca3f..1720476480e 100644
--- a/app/assets/javascripts/monitoring/components/graph/flag.vue
+++ b/app/assets/javascripts/monitoring/components/graph/flag.vue
@@ -92,7 +92,8 @@ export default {
methods: {
seriesMetricValue(seriesIndex, series) {
const indexFromCoordinates = this.currentCoordinates[series.metricTag]
- ? this.currentCoordinates[series.metricTag].currentDataIndex : 0;
+ ? this.currentCoordinates[series.metricTag].currentDataIndex
+ : 0;
const index = this.deploymentFlagData
? this.deploymentFlagData.seriesIndex
: indexFromCoordinates;
diff --git a/app/assets/javascripts/monitoring/components/graph/track_info.vue b/app/assets/javascripts/monitoring/components/graph/track_info.vue
index ec1c2222af9..3464067834f 100644
--- a/app/assets/javascripts/monitoring/components/graph/track_info.vue
+++ b/app/assets/javascripts/monitoring/components/graph/track_info.vue
@@ -26,4 +26,3 @@ export default {
{{ summaryMetrics }}
</span>
</template>
-
diff --git a/app/assets/javascripts/monitoring/components/graph/track_line.vue b/app/assets/javascripts/monitoring/components/graph/track_line.vue
index ba3f93b39ff..e04fd9c1f35 100644
--- a/app/assets/javascripts/monitoring/components/graph/track_line.vue
+++ b/app/assets/javascripts/monitoring/components/graph/track_line.vue
@@ -33,4 +33,3 @@ export default {
</svg>
</td>
</template>
-
diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
index 007451d5c7a..87c3d969de4 100644
--- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
+++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js
@@ -6,7 +6,7 @@ const mixins = {
if (!this.reducedDeploymentData) return false;
let dataFound = false;
- this.reducedDeploymentData = this.reducedDeploymentData.map((d) => {
+ this.reducedDeploymentData = this.reducedDeploymentData.map(d => {
const deployment = d;
if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
dataFound = d.xPos + 1;
@@ -61,7 +61,7 @@ const mixins = {
this.currentCoordinates = {};
- this.seriesUnderMouse.forEach((series) => {
+ this.seriesUnderMouse.forEach(series => {
const currentDataIndex = bisectDate(series.values, this.hoverData.hoveredDate);
const currentData = series.values[currentDataIndex];
const currentX = Math.floor(series.timeSeriesScaleX(currentData.time));
diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js
index 260d424378e..24b4acaf6da 100644
--- a/app/assets/javascripts/monitoring/services/monitoring_service.js
+++ b/app/assets/javascripts/monitoring/services/monitoring_service.js
@@ -8,18 +8,20 @@ const MAX_REQUESTS = 3;
function backOffRequest(makeRequestCallback) {
let requestCounter = 0;
return backOff((next, stop) => {
- makeRequestCallback().then((resp) => {
- if (resp.status === statusCodes.NO_CONTENT) {
- requestCounter += 1;
- if (requestCounter < MAX_REQUESTS) {
- next();
+ makeRequestCallback()
+ .then(resp => {
+ if (resp.status === statusCodes.NO_CONTENT) {
+ requestCounter += 1;
+ if (requestCounter < MAX_REQUESTS) {
+ next();
+ } else {
+ stop(new Error('Failed to connect to the prometheus server'));
+ }
} else {
- stop(new Error('Failed to connect to the prometheus server'));
+ stop(resp);
}
- } else {
- stop(resp);
- }
- }).catch(stop);
+ })
+ .catch(stop);
});
}
@@ -33,7 +35,7 @@ export default class MonitoringService {
getGraphsData() {
return backOffRequest(() => axios.get(this.metricsEndpoint))
.then(resp => resp.data)
- .then((response) => {
+ .then(response => {
if (!response || !response.data) {
throw new Error(s__('Metrics|Unexpected metrics data response from prometheus endpoint'));
}
@@ -47,22 +49,27 @@ export default class MonitoringService {
}
return backOffRequest(() => axios.get(this.deploymentEndpoint))
.then(resp => resp.data)
- .then((response) => {
+ .then(response => {
if (!response || !response.deployments) {
- throw new Error(s__('Metrics|Unexpected deployment data response from prometheus endpoint'));
+ throw new Error(
+ s__('Metrics|Unexpected deployment data response from prometheus endpoint'),
+ );
}
return response.deployments;
});
}
getEnvironmentsData() {
- return axios.get(this.environmentsEndpoint)
- .then(resp => resp.data)
- .then((response) => {
- if (!response || !response.environments) {
- throw new Error(s__('Metrics|There was an error fetching the environments data, please try again'));
- }
- return response.environments;
- });
+ return axios
+ .get(this.environmentsEndpoint)
+ .then(resp => resp.data)
+ .then(response => {
+ if (!response || !response.environments) {
+ throw new Error(
+ s__('Metrics|There was an error fetching the environments data, please try again'),
+ );
+ }
+ return response.environments;
+ });
}
}
diff --git a/app/assets/javascripts/monitoring/utils/measurements.js b/app/assets/javascripts/monitoring/utils/measurements.js
index ee866850e13..7c771f43eee 100644
--- a/app/assets/javascripts/monitoring/utils/measurements.js
+++ b/app/assets/javascripts/monitoring/utils/measurements.js
@@ -1,5 +1,6 @@
export default {
- small: { // Covers both xs and sm screen sizes
+ small: {
+ // Covers both xs and sm screen sizes
margin: {
top: 40,
right: 40,
@@ -18,7 +19,8 @@ export default {
},
axisLabelLineOffset: -20,
},
- large: { // This covers both md and lg screen sizes
+ large: {
+ // This covers both md and lg screen sizes
margin: {
top: 80,
right: 80,
diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
index d5971730e31..bb24a1acdb3 100644
--- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js
+++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js
@@ -66,7 +66,8 @@ function queryTimeSeries(query, graphDrawData, lineStyle) {
// offset the same amount as the original data
const [minX, maxX] = graphDrawData.xDom;
const offset = d3.timeMinute(minX) - Number(minX);
- const datesWithoutGaps = d3.timeSecond.every(60)
+ const datesWithoutGaps = d3.timeSecond
+ .every(60)
.range(d3.timeMinute.offset(minX, -1), maxX)
.map(d => d - offset);
@@ -208,9 +209,7 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph
const timeSeries = queries.reduce((series, query, index) => {
const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length];
- return series.concat(
- queryTimeSeries(query, graphDrawData, lineStyle),
- );
+ return series.concat(queryTimeSeries(query, graphDrawData, lineStyle));
}, []);
return {
diff --git a/app/assets/javascripts/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/notebook/cells/code/index.vue b/app/assets/javascripts/notebook/cells/code/index.vue
index 7d2a1a33b98..0691ba64f8e 100644
--- a/app/assets/javascripts/notebook/cells/code/index.vue
+++ b/app/assets/javascripts/notebook/cells/code/index.vue
@@ -1,45 +1,45 @@
<script>
- import Prism from '../../lib/highlight';
- import Prompt from '../prompt.vue';
+import Prism from '../../lib/highlight';
+import Prompt from '../prompt.vue';
- export default {
- components: {
- prompt: Prompt,
+export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
},
- props: {
- count: {
- type: Number,
- required: false,
- default: 0,
- },
- codeCssClass: {
- type: String,
- required: false,
- default: '',
- },
- type: {
- type: String,
- required: true,
- },
- rawCode: {
- type: String,
- required: true,
- },
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- code() {
- return this.rawCode;
- },
- promptType() {
- const type = this.type.split('put')[0];
-
- return type.charAt(0).toUpperCase() + type.slice(1);
- },
+ type: {
+ type: String,
+ required: true,
+ },
+ rawCode: {
+ type: String,
+ required: true,
},
- mounted() {
- Prism.highlightElement(this.$refs.code);
+ },
+ computed: {
+ code() {
+ return this.rawCode;
+ },
+ promptType() {
+ const type = this.type.split('put')[0];
+
+ return type.charAt(0).toUpperCase() + type.slice(1);
},
- };
+ },
+ mounted() {
+ Prism.highlightElement(this.$refs.code);
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index 3d09d24b6ab..5aa83db0986 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -1,12 +1,12 @@
<script>
- /* global katex */
- import marked from 'marked';
- import sanitize from 'sanitize-html';
- import Prompt from './prompt.vue';
+/* global katex */
+import marked from 'marked';
+import sanitize from 'sanitize-html';
+import Prompt from './prompt.vue';
- const renderer = new marked.Renderer();
+const renderer = new marked.Renderer();
- /*
+/*
Regex to match KaTex blocks.
Supports the following:
@@ -17,7 +17,7 @@
The matched text then goes through the KaTex renderer & then outputs the HTML
*/
- const katexRegexString = `(
+const katexRegexString = `(
^\\\\begin{[a-zA-Z]+}\\s
|
^\\$\\$
@@ -32,66 +32,69 @@
|
\\$
)
- `.replace(/\s/g, '').trim();
+ `
+ .replace(/\s/g, '')
+ .trim();
- renderer.paragraph = (t) => {
- let text = t;
- let inline = false;
+renderer.paragraph = t => {
+ let text = t;
+ let inline = false;
- if (typeof katex !== 'undefined') {
- const katexString = text.replace(/&amp;/g, '&')
- .replace(/&=&/g, '\\space=\\space')
- .replace(/<(\/?)em>/g, '_');
- const regex = new RegExp(katexRegexString, 'gi');
- const matchLocation = katexString.search(regex);
- const numberOfMatches = katexString.match(regex);
+ if (typeof katex !== 'undefined') {
+ const katexString = text
+ .replace(/&amp;/g, '&')
+ .replace(/&=&/g, '\\space=\\space')
+ .replace(/<(\/?)em>/g, '_');
+ const regex = new RegExp(katexRegexString, 'gi');
+ const matchLocation = katexString.search(regex);
+ const numberOfMatches = katexString.match(regex);
- if (numberOfMatches && numberOfMatches.length !== 0) {
- if (matchLocation > 0) {
- let matches = regex.exec(katexString);
- inline = true;
+ if (numberOfMatches && numberOfMatches.length !== 0) {
+ if (matchLocation > 0) {
+ let matches = regex.exec(katexString);
+ inline = true;
- while (matches !== null) {
- const renderedKatex = katex.renderToString(matches[0].replace(/\$/g, ''));
- text = `${text.replace(matches[0], ` ${renderedKatex}`)}`;
- matches = regex.exec(katexString);
- }
- } else {
- const matches = regex.exec(katexString);
- text = katex.renderToString(matches[2]);
+ while (matches !== null) {
+ const renderedKatex = katex.renderToString(matches[0].replace(/\$/g, ''));
+ text = `${text.replace(matches[0], ` ${renderedKatex}`)}`;
+ matches = regex.exec(katexString);
}
+ } else {
+ const matches = regex.exec(katexString);
+ text = katex.renderToString(matches[2]);
}
}
+ }
- return `<p class="${inline ? 'inline-katex' : ''}">${text}</p>`;
- };
+ return `<p class="${inline ? 'inline-katex' : ''}">${text}</p>`;
+};
- marked.setOptions({
- sanitize: true,
- renderer,
- });
+marked.setOptions({
+ sanitize: true,
+ renderer,
+});
- export default {
- components: {
- prompt: Prompt,
- },
- props: {
- cell: {
- type: Object,
- required: true,
- },
+export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ cell: {
+ type: Object,
+ required: true,
},
- computed: {
- markdown() {
- return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), {
- allowedTags: false,
- allowedAttributes: {
- '*': ['class'],
- },
- });
- },
+ },
+ computed: {
+ markdown() {
+ return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), {
+ allowedTags: false,
+ allowedAttributes: {
+ '*': ['class'],
+ },
+ });
},
- };
+ },
+};
</script>
<template>
@@ -105,13 +108,13 @@
</template>
<style>
- .markdown .katex {
- display: block;
- text-align: center;
- }
+.markdown .katex {
+ display: block;
+ text-align: center;
+}
- .markdown .inline-katex .katex {
- display: inline;
- text-align: initial;
- }
+.markdown .inline-katex .katex {
+ display: inline;
+ text-align: initial;
+}
</style>
diff --git a/app/assets/javascripts/notebook/cells/output/html.vue b/app/assets/javascripts/notebook/cells/output/html.vue
index 0535ee7afa8..c6fc786fa76 100644
--- a/app/assets/javascripts/notebook/cells/output/html.vue
+++ b/app/assets/javascripts/notebook/cells/output/html.vue
@@ -1,30 +1,28 @@
<script>
- import sanitize from 'sanitize-html';
- import Prompt from '../prompt.vue';
+import sanitize from 'sanitize-html';
+import Prompt from '../prompt.vue';
- export default {
- components: {
- prompt: Prompt,
+export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ rawCode: {
+ type: String,
+ required: true,
},
- props: {
- rawCode: {
- type: String,
- required: true,
- },
+ },
+ computed: {
+ sanitizedOutput() {
+ return sanitize(this.rawCode, {
+ allowedTags: sanitize.defaults.allowedTags.concat(['img', 'svg']),
+ allowedAttributes: {
+ img: ['src'],
+ },
+ });
},
- computed: {
- sanitizedOutput() {
- return sanitize(this.rawCode, {
- allowedTags: sanitize.defaults.allowedTags.concat([
- 'img', 'svg',
- ]),
- allowedAttributes: {
- img: ['src'],
- },
- });
- },
- },
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue
index 67d6c5ad12b..a17868963ce 100644
--- a/app/assets/javascripts/notebook/cells/output/image.vue
+++ b/app/assets/javascripts/notebook/cells/output/image.vue
@@ -1,21 +1,21 @@
<script>
- import Prompt from '../prompt.vue';
+import Prompt from '../prompt.vue';
- export default {
- components: {
- prompt: Prompt,
+export default {
+ components: {
+ prompt: Prompt,
+ },
+ props: {
+ outputType: {
+ type: String,
+ required: true,
},
- props: {
- outputType: {
- type: String,
- required: true,
- },
- rawCode: {
- type: String,
- required: true,
- },
+ rawCode: {
+ type: String,
+ required: true,
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notebook/cells/output/index.vue b/app/assets/javascripts/notebook/cells/output/index.vue
index 4183b976814..d9f8604ed10 100644
--- a/app/assets/javascripts/notebook/cells/output/index.vue
+++ b/app/assets/javascripts/notebook/cells/output/index.vue
@@ -1,78 +1,78 @@
<script>
- import CodeCell from '../code/index.vue';
- import Html from './html.vue';
- import Image from './image.vue';
+import CodeCell from '../code/index.vue';
+import Html from './html.vue';
+import Image from './image.vue';
- export default {
- components: {
- 'code-cell': CodeCell,
- 'html-output': Html,
- 'image-output': Image,
+export default {
+ components: {
+ 'code-cell': CodeCell,
+ 'html-output': Html,
+ 'image-output': Image,
+ },
+ props: {
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- codeCssClass: {
- type: String,
- required: false,
- default: '',
- },
- count: {
- type: Number,
- required: false,
- default: 0,
- },
- output: {
- type: Object,
- requred: true,
- default: () => ({}),
- },
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
},
- computed: {
- componentName() {
- if (this.output.text) {
- return 'code-cell';
- } else if (this.output.data['image/png']) {
- return 'image-output';
- } else if (this.output.data['text/html']) {
- return 'html-output';
- } else if (this.output.data['image/svg+xml']) {
- return 'html-output';
- }
-
+ output: {
+ type: Object,
+ requred: true,
+ default: () => ({}),
+ },
+ },
+ computed: {
+ componentName() {
+ if (this.output.text) {
return 'code-cell';
- },
- rawCode() {
- if (this.output.text) {
- return this.output.text.join('');
- }
+ } else if (this.output.data['image/png']) {
+ return 'image-output';
+ } else if (this.output.data['text/html']) {
+ return 'html-output';
+ } else if (this.output.data['image/svg+xml']) {
+ return 'html-output';
+ }
- return this.dataForType(this.outputType);
- },
- outputType() {
- if (this.output.text) {
- return '';
- } else if (this.output.data['image/png']) {
- return 'image/png';
- } else if (this.output.data['text/html']) {
- return 'text/html';
- } else if (this.output.data['image/svg+xml']) {
- return 'image/svg+xml';
- }
+ return 'code-cell';
+ },
+ rawCode() {
+ if (this.output.text) {
+ return this.output.text.join('');
+ }
+
+ return this.dataForType(this.outputType);
+ },
+ outputType() {
+ if (this.output.text) {
+ return '';
+ } else if (this.output.data['image/png']) {
+ return 'image/png';
+ } else if (this.output.data['text/html']) {
+ return 'text/html';
+ } else if (this.output.data['image/svg+xml']) {
+ return 'image/svg+xml';
+ }
- return 'text/plain';
- },
+ return 'text/plain';
},
- methods: {
- dataForType(type) {
- let data = this.output.data[type];
+ },
+ methods: {
+ dataForType(type) {
+ let data = this.output.data[type];
- if (typeof data === 'object') {
- data = data.join('');
- }
+ if (typeof data === 'object') {
+ data = data.join('');
+ }
- return data;
- },
+ return data;
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notebook/cells/prompt.vue b/app/assets/javascripts/notebook/cells/prompt.vue
index fe1fc37e1dc..d96f701ee3e 100644
--- a/app/assets/javascripts/notebook/cells/prompt.vue
+++ b/app/assets/javascripts/notebook/cells/prompt.vue
@@ -1,23 +1,23 @@
<script>
- export default {
- props: {
- type: {
- type: String,
- required: false,
- default: '',
- },
- count: {
- type: Number,
- required: false,
- default: 0,
- },
+export default {
+ props: {
+ type: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- hasKeys() {
- return this.type !== '' && this.count;
- },
+ count: {
+ type: Number,
+ required: false,
+ default: 0,
},
- };
+ },
+ computed: {
+ hasKeys() {
+ return this.type !== '' && this.count;
+ },
+ },
+};
</script>
<template>
@@ -29,9 +29,9 @@
</template>
<style scoped>
- .prompt {
- padding: 0 10px;
- min-width: 7em;
- font-family: monospace;
- }
+.prompt {
+ padding: 0 10px;
+ min-width: 7em;
+ font-family: monospace;
+}
</style>
diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue
index f241df9620d..c5cc8c97dda 100644
--- a/app/assets/javascripts/notebook/index.vue
+++ b/app/assets/javascripts/notebook/index.vue
@@ -1,51 +1,48 @@
<script>
- import {
- MarkdownCell,
- CodeCell,
- } from './cells';
+import { MarkdownCell, CodeCell } from './cells';
- export default {
- components: {
- 'code-cell': CodeCell,
- 'markdown-cell': MarkdownCell,
+export default {
+ components: {
+ 'code-cell': CodeCell,
+ 'markdown-cell': MarkdownCell,
+ },
+ props: {
+ notebook: {
+ type: Object,
+ required: true,
},
- props: {
- notebook: {
- type: Object,
- required: true,
- },
- codeCssClass: {
- type: String,
- required: false,
- default: '',
- },
+ codeCssClass: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- cells() {
- if (this.notebook.worksheets) {
- const data = {
- cells: [],
- };
+ },
+ computed: {
+ cells() {
+ if (this.notebook.worksheets) {
+ const data = {
+ cells: [],
+ };
- return this.notebook.worksheets.reduce((cellData, sheet) => {
- const cellDataCopy = cellData;
- cellDataCopy.cells = cellDataCopy.cells.concat(sheet.cells);
- return cellDataCopy;
- }, data).cells;
- }
+ return this.notebook.worksheets.reduce((cellData, sheet) => {
+ const cellDataCopy = cellData;
+ cellDataCopy.cells = cellDataCopy.cells.concat(sheet.cells);
+ return cellDataCopy;
+ }, data).cells;
+ }
- return this.notebook.cells;
- },
- hasNotebook() {
- return Object.keys(this.notebook).length;
- },
+ return this.notebook.cells;
},
- methods: {
- cellType(type) {
- return `${type}-cell`;
- },
+ hasNotebook() {
+ return Object.keys(this.notebook).length;
},
- };
+ },
+ methods: {
+ cellType(type) {
+ return `${type}-cell`;
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 353aa790743..d9e99603238 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -94,6 +94,7 @@ export default {
class="diff-file file-holder"
>
<diff-file-header
+ :discussion-path="discussion.discussionPath"
:diff-file="diffFile"
:can-current-user-fork="false"
:discussions-expanded="isDiscussionsExpanded"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 33998394a69..38c43e5fe08 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -102,6 +102,18 @@ export default {
},
methods: {
...mapActions(['toggleResolveNote']),
+ shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState) {
+ // shouldBeResolved() checks the actual resolution state,
+ // considering batchComments (EEP), if applicable/enabled.
+ const newResolvedStateAfterUpdate =
+ this.shouldBeResolved && this.shouldBeResolved(shouldResolve);
+
+ const shouldToggleState =
+ newResolvedStateAfterUpdate !== undefined &&
+ beforeSubmitDiscussionState !== newResolvedStateAfterUpdate;
+
+ return shouldResolve || shouldToggleState;
+ },
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
@@ -109,7 +121,7 @@ export default {
this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => {
this.isSubmitting = false;
- if (shouldResolve) {
+ if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
this.resolveHandler(beforeSubmitDiscussionState);
}
});
diff --git a/app/assets/javascripts/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..3b58c54b3f4 100644
--- a/app/assets/javascripts/pager.js
+++ b/app/assets/javascripts/pager.js
@@ -24,22 +24,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/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
index 002b2279fcc..32b55575f95 100644
--- a/app/assets/javascripts/pages/groups/edit/index.js
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -2,13 +2,20 @@ import groupAvatar from '~/group_avatar';
import TransferDropdown from '~/groups/transfer_dropdown';
import initConfirmDangerModal from '~/confirm_danger_modal';
import initSettingsPanels from '~/settings_panels';
+import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import { GROUP_BADGE } from '~/badges/constants';
+import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => {
groupAvatar();
new TransferDropdown(); // eslint-disable-line no-new
initConfirmDangerModal();
initSettingsPanels();
+ dirtySubmitFactory(
+ document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'),
+ );
mountBadgeSettings(GROUP_BADGE);
+
+ projectSelect();
});
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index b0345b4e50d..5659e13981a 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,5 +1,5 @@
+import initDismissableCallout from '~/dismissable_callout';
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
-import PersistentUserCallout from '../../persistent_user_callout';
import Project from './project';
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
@@ -12,9 +12,7 @@ document.addEventListener('DOMContentLoaded', () => {
];
if (newClusterViews.indexOf(page) > -1) {
- const callout = document.querySelector('.gcp-signup-offer');
- if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
-
+ initDismissableCallout('.gcp-signup-offer');
initGkeDropdowns();
}
diff --git a/app/assets/javascripts/pages/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/root/index.js b/app/assets/javascripts/pages/root/index.js
deleted file mode 100644
index 09f8185d3b5..00000000000
--- a/app/assets/javascripts/pages/root/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// if the "projects dashboard" is a user's default dashboard, when they visit the
-// instance root index, the dashboard will be served by the root controller instead
-// of a dashboard controller. The root index redirects for all other default dashboards.
-
-import '../dashboard/projects/index';
diff --git a/app/assets/javascripts/pages/users/user_overview_block.js b/app/assets/javascripts/pages/users/user_overview_block.js
index 0009419cd0c..2ed177be558 100644
--- a/app/assets/javascripts/pages/users/user_overview_block.js
+++ b/app/assets/javascripts/pages/users/user_overview_block.js
@@ -1,10 +1,15 @@
import axios from '~/lib/utils/axios_utils';
+const DEFAULT_LIMIT = 20;
+
export default class UserOverviewBlock {
constructor(options = {}) {
this.container = options.container;
this.url = options.url;
- this.limit = options.limit || 20;
+ this.requestParams = {
+ limit: DEFAULT_LIMIT,
+ ...options.requestParams,
+ };
this.loadData();
}
@@ -15,9 +20,7 @@ export default class UserOverviewBlock {
axios
.get(this.url, {
- params: {
- limit: this.limit,
- },
+ params: this.requestParams,
})
.then(({ data }) => this.render(data))
.catch(() => loadingEl.classList.add('hide'));
@@ -34,7 +37,9 @@ export default class UserOverviewBlock {
if (count && count > 0) {
document.querySelector(`${this.container} .js-view-all`).classList.remove('hide');
} else {
- document.querySelector(`${this.container} .nothing-here-block`).classList.add('text-left', 'p-0');
+ document
+ .querySelector(`${this.container} .nothing-here-block`)
+ .classList.add('text-left', 'p-0');
}
loadingEl.classList.add('hide');
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 23b0348a99f..1de9945baad 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -182,18 +182,22 @@ export default class UserTabs {
this.loadActivityCalendar('overview');
- UserTabs.renderMostRecentBlocks('#js-overview .activities-block', 5);
- UserTabs.renderMostRecentBlocks('#js-overview .projects-block', 10);
+ UserTabs.renderMostRecentBlocks('#js-overview .activities-block', {
+ requestParams: { limit: 5 },
+ });
+ UserTabs.renderMostRecentBlocks('#js-overview .projects-block', {
+ requestParams: { limit: 10, skip_pagination: true },
+ });
this.loaded.overview = true;
}
- static renderMostRecentBlocks(container, limit) {
+ static renderMostRecentBlocks(container, options) {
// eslint-disable-next-line no-new
new UserOverviewBlock({
container,
url: $(`${container} .overview-content-list`).data('href'),
- limit,
+ ...options,
});
}
@@ -216,7 +220,12 @@ export default class UserTabs {
let calendarHint = '';
if (action === 'activity') {
- calendarHint = sprintf(__('Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})'), { utcFormatted });
+ calendarHint = sprintf(
+ __(
+ 'Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})',
+ ),
+ { utcFormatted },
+ );
} else if (action === 'overview') {
calendarHint = __('Issues, merge requests, pushes and comments.');
}
@@ -224,7 +233,15 @@ export default class UserTabs {
$calendarWrap.find('.calendar-hint').text(calendarHint);
// eslint-disable-next-line no-new
- new ActivityCalendar('.tab-pane.active .js-contrib-calendar', '.tab-pane.active .user-calendar-activities', data, calendarActivitiesPath, utcOffset, 0, monthsAgo);
+ new ActivityCalendar(
+ '.tab-pane.active .js-contrib-calendar',
+ '.tab-pane.active .user-calendar-activities',
+ data,
+ calendarActivitiesPath,
+ utcOffset,
+ 0,
+ monthsAgo,
+ );
})
.catch(() => flash(__('There was an error loading users activity calendar.')));
}
diff --git a/app/assets/javascripts/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/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 1522e2227e4..300d453c174 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -1,5 +1,6 @@
<script>
import $ from 'jquery';
+import { glEmojiTag } from '~/emoji';
import detailedMetric from './detailed_metric.vue';
import requestSelector from './request_selector.vue';
@@ -64,6 +65,16 @@ export default {
lineProfileModal() {
return $('#modal-peek-line-profile');
},
+ hasHost() {
+ return this.currentRequest && this.currentRequest.details && this.currentRequest.details.host;
+ },
+ birdEmoji() {
+ if (this.hasHost && this.currentRequest.details.host.canary) {
+ return glEmojiTag('baby_chick');
+ }
+
+ return '';
+ },
},
mounted() {
this.currentRequest = this.requestId;
@@ -93,9 +104,11 @@ export default {
class="view"
>
<span
- v-if="currentRequest.details"
+ v-if="hasHost"
class="current-host"
+ :class="{ 'canary' : currentRequest.details.host.canary }"
>
+ <span v-html="birdEmoji"></span>
{{ currentRequest.details.host.hostname }}
</span>
</div>
diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js
deleted file mode 100644
index 1e34e74a152..00000000000
--- a/app/assets/javascripts/persistent_user_callout.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import axios from './lib/utils/axios_utils';
-import { __ } from './locale';
-import Flash from './flash';
-
-export default class PersistentUserCallout {
- constructor(container) {
- const { dismissEndpoint, featureId } = container.dataset;
- this.container = container;
- this.dismissEndpoint = dismissEndpoint;
- this.featureId = featureId;
-
- this.init();
- }
-
- init() {
- const closeButton = this.container.querySelector('.js-close');
- closeButton.addEventListener('click', event => this.dismiss(event));
- }
-
- dismiss(event) {
- event.preventDefault();
-
- axios
- .post(this.dismissEndpoint, {
- feature_name: this.featureId,
- })
- .then(() => {
- this.container.remove();
- })
- .catch(() => {
- Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
- });
- }
-}
diff --git a/app/assets/javascripts/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 fb8c6402d02..b373d83a44b 100644
--- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
+++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue
@@ -82,7 +82,7 @@
: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/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 64f3dde5be7..6b3753f7966 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -57,8 +57,6 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
$allGutterToggleIcons.removeClass('fa-angle-double-left').addClass('fa-angle-double-right');
$('aside.right-sidebar').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
$('.layout-page').removeClass('right-sidebar-collapsed').addClass('right-sidebar-expanded');
-
- if (gl.lazyLoader) gl.lazyLoader.loadCheck();
}
$this.attr('data-original-title', tooltipLabel);
diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js
index 37b4a2a4c63..d22aca35e09 100644
--- a/app/assets/javascripts/settings_panels.js
+++ b/app/assets/javascripts/settings_panels.js
@@ -1,21 +1,27 @@
import $ from 'jquery';
+import { __ } from './locale';
function expandSection($section) {
- $section.find('.js-settings-toggle').text('Collapse');
- $section.find('.settings-content').off('scroll.expandSection').scrollTop(0);
+ $section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Collapse'));
+ $section
+ .find('.settings-content')
+ .off('scroll.expandSection')
+ .scrollTop(0);
$section.addClass('expanded');
if (!$section.hasClass('no-animate')) {
- $section.addClass('animating')
+ $section
+ .addClass('animating')
.one('animationend.animateSection', () => $section.removeClass('animating'));
}
}
function closeSection($section) {
- $section.find('.js-settings-toggle').text('Expand');
+ $section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Expand'));
$section.find('.settings-content').on('scroll.expandSection', () => expandSection($section));
$section.removeClass('expanded');
if (!$section.hasClass('no-animate')) {
- $section.addClass('animating')
+ $section
+ .addClass('animating')
.one('animationend.animateSection', () => $section.removeClass('animating'));
}
}
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index ab7fab7e5ca..cdff4105335 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -102,7 +102,6 @@ export default {
>
<icon
:name="lockIcon"
- aria-hidden="true"
class="sidebar-item-icon is-active"
/>
</div>
@@ -134,7 +133,6 @@ export default {
<icon
:size="16"
name="lock"
- aria-hidden="true"
class="sidebar-item-icon inline is-active"
/>
{{ __('Locked') }}
@@ -147,7 +145,6 @@ export default {
<icon
:size="16"
name="lock-open"
- aria-hidden="true"
class="sidebar-item-icon inline"
/>
{{ __('Unlocked') }}
diff --git a/app/assets/javascripts/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 70518ad73e8..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,13 +19,12 @@ export default {
StatusIcon,
Icon,
TooltipOnTruncate,
+ FilteredSearchDropdown,
},
directives: {
tooltip,
},
- mixins: [
- timeagoMixin,
- ],
+ mixins: [timeagoMixin],
props: {
deployment: {
type: Object,
@@ -32,8 +32,10 @@ export default {
},
},
data() {
+ const features = window.gon.features || {};
return {
isStopping: false,
+ enableCiEnvironmentsStatusChanges: features.ciEnvironmentsStatusChanges,
};
},
computed: {
@@ -50,7 +52,7 @@ export default {
return !!(this.deployment.url && this.deployment.name);
},
hasMetrics() {
- return !!(this.deployment.metrics_url);
+ return !!this.deployment.metrics_url;
},
},
methods: {
@@ -63,7 +65,7 @@ export default {
MRWidgetService.stopEnvironment(this.deployment.stop_url)
.then(res => res.data)
- .then((data) => {
+ .then(data => {
if (data.redirect_url) {
visitUrl(data.redirect_url);
}
@@ -120,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/components/memory_usage.vue b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
index 5e76f6b1cac..41dbc5c9cbb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/memory_usage.vue
@@ -60,11 +60,29 @@ export default {
let memoryUsageMsg = '';
if (memoryTo > memoryFrom) {
- memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB'), messageProps, false);
+ memoryUsageMsg = sprintf(
+ s__(
+ 'mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} increased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB',
+ ),
+ messageProps,
+ false,
+ );
} else if (memoryTo < memoryFrom) {
- memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB'), messageProps, false);
+ memoryUsageMsg = sprintf(
+ s__(
+ 'mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB',
+ ),
+ messageProps,
+ false,
+ );
} else {
- memoryUsageMsg = sprintf(s__('mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB'), messageProps, false);
+ memoryUsageMsg = sprintf(
+ s__(
+ 'mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage is %{emphasisStart} unchanged %{emphasisEnd} at %{memoryFrom}MB',
+ ),
+ messageProps,
+ false,
+ );
}
return memoryUsageMsg;
@@ -77,7 +95,7 @@ export default {
methods: {
getMegabytes(bytesString) {
const valueInBytes = Number(bytesString).toFixed(2);
- return (bytesToMiB(valueInBytes)).toFixed(2);
+ return bytesToMiB(valueInBytes).toFixed(2);
},
computeGraphData(metrics, deploymentTime) {
this.loadingMetrics = false;
@@ -103,7 +121,7 @@ export default {
loadMetrics() {
backOff((next, stop) => {
MRWidgetService.fetchMetrics(this.metricsUrl)
- .then((res) => {
+ .then(res => {
if (res.status === statusCodes.NO_CONTENT) {
this.backOffRequestCounter += 1;
/* eslint-disable no-unused-expressions */
@@ -114,14 +132,14 @@ export default {
})
.catch(stop);
})
- .then((res) => {
+ .then(res => {
if (res.status === statusCodes.NO_CONTENT) {
return res;
}
return res.data;
})
- .then((data) => {
+ .then(data => {
this.computeGraphData(data.metrics, data.deployment_time);
return data;
})
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
index 22c2f74f900..2ad9e8be655 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
@@ -1,36 +1,36 @@
<script>
- import tooltip from '../../vue_shared/directives/tooltip';
+import tooltip from '../../vue_shared/directives/tooltip';
- export default {
- name: 'MrWidgetAuthor',
- directives: {
- tooltip,
+export default {
+ name: 'MrWidgetAuthor',
+ directives: {
+ tooltip,
+ },
+ props: {
+ author: {
+ type: Object,
+ required: true,
},
- props: {
- author: {
- type: Object,
- required: true,
- },
- showAuthorName: {
- type: Boolean,
- required: false,
- default: true,
- },
- showAuthorTooltip: {
- type: Boolean,
- required: false,
- default: false,
- },
+ showAuthorName: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- computed: {
- authorUrl() {
- return this.author.webUrl || this.author.web_url;
- },
- avatarUrl() {
- return this.author.avatarUrl || this.author.avatar_url;
- },
+ showAuthorTooltip: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- };
+ },
+ computed: {
+ authorUrl() {
+ return this.author.webUrl || this.author.web_url;
+ },
+ avatarUrl() {
+ return this.author.avatarUrl || this.author.avatar_url;
+ },
+ },
+};
</script>
<template>
<a
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue
index ba16cb9e2c8..1d902131f49 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue
@@ -1,34 +1,34 @@
<script>
- import tooltip from '~/vue_shared/directives/tooltip';
- import MrWidgetAuthor from './mr_widget_author.vue';
+import tooltip from '~/vue_shared/directives/tooltip';
+import MrWidgetAuthor from './mr_widget_author.vue';
- export default {
- name: 'MrWidgetAuthorTime',
- components: {
- MrWidgetAuthor,
+export default {
+ name: 'MrWidgetAuthorTime',
+ components: {
+ MrWidgetAuthor,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ actionText: {
+ type: String,
+ required: true,
},
- directives: {
- tooltip,
+ author: {
+ type: Object,
+ required: true,
},
- props: {
- actionText: {
- type: String,
- required: true,
- },
- author: {
- type: Object,
- required: true,
- },
- dateTitle: {
- type: String,
- required: true,
- },
- dateReadable: {
- type: String,
- required: true,
- },
+ dateTitle: {
+ type: String,
+ required: true,
},
- };
+ dateReadable: {
+ type: String,
+ required: true,
+ },
+ },
+};
</script>
<template>
<h4 class="js-mr-widget-author">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
index 62b61e1f41f..37c6af13c03 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
@@ -1,24 +1,26 @@
<script>
- import { sprintf, s__ } from '~/locale';
+import { sprintf, s__ } from '~/locale';
- export default {
- name: 'MRWidgetMergeHelp',
- props: {
- missingBranch: {
- type: String,
- required: false,
- default: '',
- },
+export default {
+ name: 'MRWidgetMergeHelp',
+ props: {
+ missingBranch: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- missingBranchInfo() {
- return sprintf(
- s__('mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the'),
- { branch: this.missingBranch },
- );
- },
+ },
+ computed: {
+ missingBranchInfo() {
+ return sprintf(
+ s__(
+ 'mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the',
+ ),
+ { branch: this.missingBranch },
+ );
},
- };
+ },
+};
</script>
<template>
<section class="mr-widget-help">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
index 88d0fcd70f5..cc77b96a589 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue
@@ -1,32 +1,32 @@
<script>
- import { s__ } from '~/locale';
+import { s__ } from '~/locale';
- export default {
- name: 'MRWidgetRelatedLinks',
- props: {
- relatedLinks: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- state: {
- type: String,
- required: false,
- default: '',
- },
+export default {
+ name: 'MRWidgetRelatedLinks',
+ props: {
+ relatedLinks: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- computed: {
- closesText() {
- if (this.state === 'merged') {
- return s__('mrWidget|Closed');
- }
- if (this.state === 'closed') {
- return s__('mrWidget|Did not close');
- }
- return s__('mrWidget|Closes');
- },
+ state: {
+ type: String,
+ required: false,
+ default: '',
},
- };
+ },
+ computed: {
+ closesText() {
+ if (this.state === 'merged') {
+ return s__('mrWidget|Closed');
+ }
+ if (this.state === 'closed') {
+ return s__('mrWidget|Did not close');
+ }
+ return s__('mrWidget|Closes');
+ },
+ },
+};
</script>
<template>
<section class="mr-info-list mr-links">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index 035ae791a1d..ba6a1687e51 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -1,33 +1,33 @@
<script>
- import ciIcon from '../../vue_shared/components/ci_icon.vue';
+import ciIcon from '../../vue_shared/components/ci_icon.vue';
- export default {
- components: {
- ciIcon,
+export default {
+ components: {
+ ciIcon,
+ },
+ props: {
+ status: {
+ type: String,
+ required: true,
},
- props: {
- status: {
- type: String,
- required: true,
- },
- showDisabledButton: {
- type: Boolean,
- required: false,
- default: false,
- },
+ showDisabledButton: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- computed: {
- isLoading() {
- return this.status === 'loading';
- },
- statusObj() {
- return {
- group: this.status,
- icon: `status_${this.status}`,
- };
- },
+ },
+ computed: {
+ isLoading() {
+ return this.status === 'loading';
},
- };
+ statusObj() {
+ return {
+ group: this.status,
+ icon: `status_${this.status}`,
+ };
+ },
+ },
+};
</script>
<template>
<div class="space-children d-flex append-right-10 widget-status-icon">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
index 56879c04d16..01f707163d4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/source_branch_removal_status.vue
@@ -1,16 +1,16 @@
<script>
- import tooltip from '../../vue_shared/directives/tooltip';
- import { __ } from '../../locale';
+import tooltip from '../../vue_shared/directives/tooltip';
+import { __ } from '../../locale';
- export default {
- directives: {
- tooltip,
- },
- created() {
- this.removesBranchText = __('<strong>Removes</strong> source branch');
- this.tooltipTitle = __('A user with write access to the source branch selected this option');
- },
- };
+export default {
+ directives: {
+ tooltip,
+ },
+ created() {
+ this.removesBranchText = __('<strong>Removes</strong> source branch');
+ this.tooltipTitle = __('A user with write access to the source branch selected this option');
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
index cfbd44d41b2..2a76a878448 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
@@ -1,12 +1,12 @@
<script>
- import statusIcon from '../mr_widget_status_icon.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetArchived',
- components: {
- statusIcon,
- },
- };
+export default {
+ name: 'MRWidgetArchived',
+ components: {
+ statusIcon,
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
index 01294d5b40c..4f8b07484c0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue
@@ -1,32 +1,32 @@
<script>
- import eventHub from '../../event_hub';
- import statusIcon from '../mr_widget_status_icon.vue';
+import eventHub from '../../event_hub';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetAutoMergeFailed',
- components: {
- statusIcon,
+export default {
+ name: 'MRWidgetAutoMergeFailed',
+ components: {
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
},
- props: {
- mr: {
- type: Object,
- required: true,
- },
+ },
+ data() {
+ return {
+ isRefreshing: false,
+ };
+ },
+ methods: {
+ refreshWidget() {
+ this.isRefreshing = true;
+ eventHub.$emit('MRWidgetUpdateRequested', () => {
+ this.isRefreshing = false;
+ });
},
- data() {
- return {
- isRefreshing: false,
- };
- },
- methods: {
- refreshWidget() {
- this.isRefreshing = true;
- eventHub.$emit('MRWidgetUpdateRequested', () => {
- this.isRefreshing = false;
- });
- },
- },
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
index ae6630dcd6f..fdf0a9fd994 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
@@ -1,12 +1,12 @@
<script>
- import statusIcon from '../mr_widget_status_icon.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetChecking',
- components: {
- statusIcon,
- },
- };
+export default {
+ name: 'MRWidgetChecking',
+ components: {
+ statusIcon,
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
index 25a57e520ee..f06eab95c7e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
@@ -1,23 +1,23 @@
<script>
- import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
- import statusIcon from '../mr_widget_status_icon.vue';
+import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetClosed',
- components: {
- MrWidgetAuthorTime,
- statusIcon,
- },
- props: {
- /* TODO: This is providing all store and service down when it
+export default {
+ name: 'MRWidgetClosed',
+ components: {
+ MrWidgetAuthorTime,
+ statusIcon,
+ },
+ props: {
+ /* TODO: This is providing all store and service down when it
only needs metrics and targetBranch */
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
index dff9ec657b9..8c808296178 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -1,21 +1,21 @@
<script>
- import statusIcon from '../mr_widget_status_icon.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetConflicts',
- components: {
- statusIcon,
- },
- props: {
- /* TODO: This is providing all store and service down when it
+export default {
+ name: 'MRWidgetConflicts',
+ components: {
+ statusIcon,
+ },
+ props: {
+ /* TODO: This is providing all store and service down when it
only needs a few props */
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
index 97f4196b94d..484b5600d63 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue
@@ -1,82 +1,82 @@
<script>
- import Flash from '../../../flash';
- import statusIcon from '../mr_widget_status_icon.vue';
- import MrWidgetAuthor from '../../components/mr_widget_author.vue';
- import eventHub from '../../event_hub';
+import Flash from '../../../flash';
+import statusIcon from '../mr_widget_status_icon.vue';
+import MrWidgetAuthor from '../../components/mr_widget_author.vue';
+import eventHub from '../../event_hub';
- export default {
- name: 'MRWidgetMergeWhenPipelineSucceeds',
- components: {
- MrWidgetAuthor,
- statusIcon,
+export default {
+ name: 'MRWidgetMergeWhenPipelineSucceeds',
+ components: {
+ MrWidgetAuthor,
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- props: {
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- service: {
- type: Object,
- required: true,
- default: () => ({}),
- },
+ service: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- data() {
- return {
- isCancellingAutoMerge: false,
- isRemovingSourceBranch: false,
- };
- },
- computed: {
- canRemoveSourceBranch() {
- const {
- shouldRemoveSourceBranch,
- canRemoveSourceBranch,
- mergeUserId,
- currentUserId,
- } = this.mr;
+ },
+ data() {
+ return {
+ isCancellingAutoMerge: false,
+ isRemovingSourceBranch: false,
+ };
+ },
+ computed: {
+ canRemoveSourceBranch() {
+ const {
+ shouldRemoveSourceBranch,
+ canRemoveSourceBranch,
+ mergeUserId,
+ currentUserId,
+ } = this.mr;
- return !shouldRemoveSourceBranch &&
- canRemoveSourceBranch &&
- mergeUserId === currentUserId;
- },
+ return !shouldRemoveSourceBranch && canRemoveSourceBranch && mergeUserId === currentUserId;
},
- methods: {
- cancelAutomaticMerge() {
- this.isCancellingAutoMerge = true;
- this.service.cancelAutomaticMerge()
- .then(res => res.data)
- .then((data) => {
- eventHub.$emit('UpdateWidgetData', data);
- })
- .catch(() => {
- this.isCancellingAutoMerge = false;
- Flash('Something went wrong. Please try again.');
- });
- },
- removeSourceBranch() {
- const options = {
- sha: this.mr.sha,
- merge_when_pipeline_succeeds: true,
- should_remove_source_branch: true,
- };
+ },
+ methods: {
+ cancelAutomaticMerge() {
+ this.isCancellingAutoMerge = true;
+ this.service
+ .cancelAutomaticMerge()
+ .then(res => res.data)
+ .then(data => {
+ eventHub.$emit('UpdateWidgetData', data);
+ })
+ .catch(() => {
+ this.isCancellingAutoMerge = false;
+ Flash('Something went wrong. Please try again.');
+ });
+ },
+ removeSourceBranch() {
+ const options = {
+ sha: this.mr.sha,
+ merge_when_pipeline_succeeds: true,
+ should_remove_source_branch: true,
+ };
- this.isRemovingSourceBranch = true;
- this.service.merge(options)
- .then(res => res.data)
- .then((data) => {
- if (data.status === 'merge_when_pipeline_succeeds') {
- eventHub.$emit('MRWidgetUpdateRequested');
- }
- })
- .catch(() => {
- this.isRemovingSourceBranch = false;
- Flash('Something went wrong. Please try again.');
- });
- },
+ this.isRemovingSourceBranch = true;
+ this.service
+ .merge(options)
+ .then(res => res.data)
+ .then(data => {
+ if (data.status === 'merge_when_pipeline_succeeds') {
+ eventHub.$emit('MRWidgetUpdateRequested');
+ }
+ })
+ .catch(() => {
+ this.isRemovingSourceBranch = false;
+ Flash('Something went wrong. Please try again.');
+ });
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
index 8184ef33022..656c3b5c47e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
@@ -1,106 +1,100 @@
<script>
- import Flash from '~/flash';
- import tooltip from '~/vue_shared/directives/tooltip';
- import { s__, __ } from '~/locale';
- import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
- import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
- import statusIcon from '../mr_widget_status_icon.vue';
- import eventHub from '../../event_hub';
+import Flash from '~/flash';
+import tooltip from '~/vue_shared/directives/tooltip';
+import { s__, __ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
+import eventHub from '../../event_hub';
- export default {
- name: 'MRWidgetMerged',
- directives: {
- tooltip,
+export default {
+ name: 'MRWidgetMerged',
+ directives: {
+ tooltip,
+ },
+ components: {
+ MrWidgetAuthorTime,
+ statusIcon,
+ ClipboardButton,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- components: {
- MrWidgetAuthorTime,
- statusIcon,
- ClipboardButton,
+ service: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- props: {
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- service: {
- type: Object,
- required: true,
- default: () => ({}),
- },
+ },
+ data() {
+ return {
+ isMakingRequest: false,
+ };
+ },
+ computed: {
+ shouldShowRemoveSourceBranch() {
+ const { sourceBranchRemoved, isRemovingSourceBranch, canRemoveSourceBranch } = this.mr;
+
+ return (
+ !sourceBranchRemoved &&
+ canRemoveSourceBranch &&
+ !this.isMakingRequest &&
+ !isRemovingSourceBranch
+ );
},
- data() {
- return {
- isMakingRequest: false,
- };
+ shouldShowSourceBranchRemoving() {
+ const { sourceBranchRemoved, isRemovingSourceBranch } = this.mr;
+ return !sourceBranchRemoved && (isRemovingSourceBranch || this.isMakingRequest);
},
- computed: {
- shouldShowRemoveSourceBranch() {
- const {
- sourceBranchRemoved,
- isRemovingSourceBranch,
- canRemoveSourceBranch,
- } = this.mr;
-
- return !sourceBranchRemoved &&
- canRemoveSourceBranch &&
- !this.isMakingRequest &&
- !isRemovingSourceBranch;
- },
- shouldShowSourceBranchRemoving() {
- const {
- sourceBranchRemoved,
- isRemovingSourceBranch,
- } = this.mr;
- return !sourceBranchRemoved &&
- (isRemovingSourceBranch || this.isMakingRequest);
- },
- shouldShowMergedButtons() {
- const {
- canRevertInCurrentMR,
- canCherryPickInCurrentMR,
- revertInForkPath,
- cherryPickInForkPath,
- } = this.mr;
+ shouldShowMergedButtons() {
+ const {
+ canRevertInCurrentMR,
+ canCherryPickInCurrentMR,
+ revertInForkPath,
+ cherryPickInForkPath,
+ } = this.mr;
- return canRevertInCurrentMR ||
- canCherryPickInCurrentMR ||
- revertInForkPath ||
- cherryPickInForkPath;
- },
- revertTitle() {
- return s__('mrWidget|Revert this merge request in a new merge request');
- },
- cherryPickTitle() {
- return s__('mrWidget|Cherry-pick this merge request in a new merge request');
- },
- revertLabel() {
- return s__('mrWidget|Revert');
- },
- cherryPickLabel() {
- return s__('mrWidget|Cherry-pick');
- },
+ return (
+ canRevertInCurrentMR || canCherryPickInCurrentMR || revertInForkPath || cherryPickInForkPath
+ );
+ },
+ revertTitle() {
+ return s__('mrWidget|Revert this merge request in a new merge request');
+ },
+ cherryPickTitle() {
+ return s__('mrWidget|Cherry-pick this merge request in a new merge request');
},
- methods: {
- removeSourceBranch() {
- this.isMakingRequest = true;
+ revertLabel() {
+ return s__('mrWidget|Revert');
+ },
+ cherryPickLabel() {
+ return s__('mrWidget|Cherry-pick');
+ },
+ },
+ methods: {
+ removeSourceBranch() {
+ this.isMakingRequest = true;
- this.service.removeSourceBranch()
- .then(res => res.data)
- .then((data) => {
- if (data.message === 'Branch was removed') {
- eventHub.$emit('MRWidgetUpdateRequested', () => {
- this.isMakingRequest = false;
- });
- }
- })
- .catch(() => {
- this.isMakingRequest = false;
- Flash(__('Something went wrong. Please try again.'));
- });
- },
+ this.service
+ .removeSourceBranch()
+ .then(res => res.data)
+ .then(data => {
+ if (data.message === 'Branch was removed') {
+ eventHub.$emit('MRWidgetUpdateRequested', () => {
+ this.isMakingRequest = false;
+ });
+ }
+ })
+ .catch(() => {
+ this.isMakingRequest = false;
+ Flash(__('Something went wrong. Please try again.'));
+ });
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
@@ -162,18 +156,20 @@
<span class="label-branch">
<a :href="mr.targetBranchPath">{{ mr.targetBranch }}</a>
</span>
- with
- <a
- :href="mr.mergeCommitPath"
- class="commit-sha js-mr-merged-commit-sha"
- v-text="mr.shortMergeCommitSha"
- >
- </a>
- <clipboard-button
- :title="__('Copy commit SHA to clipboard')"
- :text="mr.mergeCommitSha"
- css-class="btn-default btn-transparent btn-clipboard js-mr-merged-copy-sha"
- />
+ <template v-if="mr.mergeCommitSha">
+ with
+ <a
+ :href="mr.mergeCommitPath"
+ class="commit-sha js-mr-merged-commit-sha"
+ v-text="mr.shortMergeCommitSha"
+ >
+ </a>
+ <clipboard-button
+ :title="__('Copy commit SHA to clipboard')"
+ :text="mr.mergeCommitSha"
+ css-class="btn-default btn-transparent btn-clipboard js-mr-merged-copy-sha"
+ />
+ </template>
</p>
<p v-if="mr.sourceBranchRemoved">
{{ s__("mrWidget|The source branch has been removed") }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
index 953ddf40a51..139e64d1878 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merging.vue
@@ -1,19 +1,19 @@
<script>
- import statusIcon from '../mr_widget_status_icon.vue';
+import statusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetMerging',
- components: {
- statusIcon,
+export default {
+ name: 'MRWidgetMerging',
+ components: {
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
},
- props: {
- mr: {
- type: Object,
- required: true,
- default: () => ({}),
- },
- },
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body mr-state-locked media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
index b0e96f74626..227e9b85f9d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
@@ -1,40 +1,48 @@
<script>
- import { sprintf, s__ } from '~/locale';
- import tooltip from '~/vue_shared/directives/tooltip';
- import statusIcon from '../mr_widget_status_icon.vue';
- import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
+import { sprintf, s__ } from '~/locale';
+import tooltip from '~/vue_shared/directives/tooltip';
+import statusIcon from '../mr_widget_status_icon.vue';
+import mrWidgetMergeHelp from '../../components/mr_widget_merge_help.vue';
- export default {
- name: 'MRWidgetMissingBranch',
- directives: {
- tooltip,
+export default {
+ name: 'MRWidgetMissingBranch',
+ directives: {
+ tooltip,
+ },
+ components: {
+ mrWidgetMergeHelp,
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
},
- components: {
- mrWidgetMergeHelp,
- statusIcon,
+ },
+ computed: {
+ missingBranchName() {
+ return this.mr.sourceBranchRemoved ? 'source' : 'target';
},
- props: {
- mr: {
- type: Object,
- required: true,
- },
- },
- computed: {
- missingBranchName() {
- return this.mr.sourceBranchRemoved ? 'source' : 'target';
- },
- missingBranchNameMessage() {
- return sprintf(s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'), {
+ missingBranchNameMessage() {
+ return sprintf(
+ s__('mrWidget| Please restore it or use a different %{missingBranchName} branch'),
+ {
missingBranchName: this.missingBranchName,
- });
- },
- message() {
- return sprintf(s__('mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line'), {
+ },
+ );
+ },
+ message() {
+ return sprintf(
+ s__(
+ 'mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line',
+ ),
+ {
missingBranchName: this.missingBranchName,
- });
- },
+ },
+ );
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue
index 92eee2cf5dd..360559ae0f0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_not_allowed.vue
@@ -1,12 +1,12 @@
<script>
- import StatusIcon from '../mr_widget_status_icon.vue';
+import StatusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetNotAllowed',
- components: {
- StatusIcon,
- },
- };
+export default {
+ name: 'MRWidgetNotAllowed',
+ components: {
+ StatusIcon,
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
index 37ee5215cea..a4eb5afb21c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
@@ -1,12 +1,12 @@
<script>
- import StatusIcon from '../mr_widget_status_icon.vue';
+import StatusIcon from '../mr_widget_status_icon.vue';
- export default {
- name: 'MRWidgetPipelineBlocked',
- components: {
- StatusIcon,
- },
- };
+export default {
+ name: 'MRWidgetPipelineBlocked',
+ components: {
+ StatusIcon,
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index f31c7a3edb8..041fa13a8f5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -1,85 +1,87 @@
<script>
- import simplePoll from '../../../lib/utils/simple_poll';
- import eventHub from '../../event_hub';
- import statusIcon from '../mr_widget_status_icon.vue';
- import Flash from '../../../flash';
+import simplePoll from '../../../lib/utils/simple_poll';
+import eventHub from '../../event_hub';
+import statusIcon from '../mr_widget_status_icon.vue';
+import Flash from '../../../flash';
- export default {
- name: 'MRWidgetRebase',
- components: {
- statusIcon,
+export default {
+ name: 'MRWidgetRebase',
+ components: {
+ statusIcon,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
},
- props: {
- mr: {
- type: Object,
- required: true,
- },
- service: {
- type: Object,
- required: true,
- },
+ service: {
+ type: Object,
+ required: true,
},
- data() {
- return {
- isMakingRequest: false,
- rebasingError: null,
- };
+ },
+ data() {
+ return {
+ isMakingRequest: false,
+ rebasingError: null,
+ };
+ },
+ computed: {
+ status() {
+ if (this.mr.rebaseInProgress || this.isMakingRequest) {
+ return 'loading';
+ }
+ if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) {
+ return 'warning';
+ }
+ return 'success';
},
- computed: {
- status() {
- if (this.mr.rebaseInProgress || this.isMakingRequest) {
- return 'loading';
- }
- if (!this.mr.canPushToSourceBranch && !this.mr.rebaseInProgress) {
- return 'warning';
- }
- return 'success';
- },
- showDisabledButton() {
- return ['failed', 'loading'].includes(this.status);
- },
+ showDisabledButton() {
+ return ['failed', 'loading'].includes(this.status);
},
- methods: {
- rebase() {
- this.isMakingRequest = true;
- this.rebasingError = null;
+ },
+ methods: {
+ rebase() {
+ this.isMakingRequest = true;
+ this.rebasingError = null;
- this.service.rebase()
- .then(() => {
- simplePoll(this.checkRebaseStatus);
- })
- .catch((error) => {
- this.rebasingError = error.merge_error;
+ this.service
+ .rebase()
+ .then(() => {
+ simplePoll(this.checkRebaseStatus);
+ })
+ .catch(error => {
+ this.rebasingError = error.merge_error;
+ this.isMakingRequest = false;
+ Flash('Something went wrong. Please try again.');
+ });
+ },
+ checkRebaseStatus(continuePolling, stopPolling) {
+ this.service
+ .poll()
+ .then(res => res.data)
+ .then(res => {
+ if (res.rebase_in_progress) {
+ continuePolling();
+ } else {
this.isMakingRequest = false;
- Flash('Something went wrong. Please try again.');
- });
- },
- checkRebaseStatus(continuePolling, stopPolling) {
- this.service.poll()
- .then(res => res.data)
- .then((res) => {
- if (res.rebase_in_progress) {
- continuePolling();
- } else {
- this.isMakingRequest = false;
-
- if (res.merge_error && res.merge_error.length) {
- this.rebasingError = res.merge_error;
- Flash('Something went wrong. Please try again.');
- }
- eventHub.$emit('MRWidgetUpdateRequested');
- stopPolling();
+ if (res.merge_error && res.merge_error.length) {
+ this.rebasingError = res.merge_error;
+ Flash('Something went wrong. Please try again.');
}
- })
- .catch(() => {
- this.isMakingRequest = false;
- Flash('Something went wrong. Please try again.');
+
+ eventHub.$emit('MRWidgetUpdateRequested');
stopPolling();
- });
- },
+ }
+ })
+ .catch(() => {
+ this.isMakingRequest = false;
+ Flash('Something went wrong. Please try again.');
+ stopPolling();
+ });
},
- };
+ },
+};
</script>
<template>
<div class="mr-widget-body media">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
index 89c9a41f316..9129fcbb918 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
@@ -24,9 +24,10 @@ export default {
methods: {
removeWIP() {
this.isMakingRequest = true;
- this.service.removeWIP()
+ this.service
+ .removeWIP()
.then(res => res.data)
- .then((data) => {
+ .then(data => {
eventHub.$emit('UpdateWidgetData', data);
new window.Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line
$('.merge-request .detail-page-description .title').text(this.mr.title);
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 0e445a29de4..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
@@ -1,5 +1,4 @@
<script>
-
import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval';
import createFlash from '../flash';
@@ -100,8 +99,11 @@ export default {
return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
},
shouldRenderSourceBranchRemovalStatus() {
- return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch &&
- (!this.mr.isNothingToMergeState && !this.mr.isMergedState);
+ return (
+ !this.mr.canRemoveSourceBranch &&
+ this.mr.shouldRemoveSourceBranch &&
+ (!this.mr.isNothingToMergeState && !this.mr.isMergedState)
+ );
},
},
created() {
@@ -110,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);
@@ -133,9 +136,10 @@ export default {
return new MRWidgetService(endpoints);
},
checkStatus(cb) {
- return this.service.checkStatus()
+ return this.service
+ .checkStatus()
.then(res => res.data)
- .then((data) => {
+ .then(data => {
this.handleNotification(data);
this.mr.setData(data);
this.setFaviconHelper();
@@ -172,20 +176,24 @@ export default {
return Promise.resolve();
},
fetchDeployments() {
- return this.service.fetchDeployments()
+ return this.service
+ .fetchDeployments()
.then(res => res.data)
- .then((data) => {
+ .then(data => {
if (data.length) {
this.mr.deployments = data;
}
})
.catch(() => {
- createFlash('Something went wrong while fetching the environments for this merge request. Please try again.');
+ createFlash(
+ 'Something went wrong while fetching the environments for this merge request. Please try again.',
+ );
});
},
fetchActionsContent() {
- this.service.fetchMergeActionsContent()
- .then((res) => {
+ this.service
+ .fetchMergeActionsContent()
+ .then(res => {
if (res.data) {
const el = document.createElement('div');
el.innerHTML = res.data;
@@ -212,22 +220,22 @@ export default {
this.pollingInterval.stopTimer();
},
bindEventHubListeners() {
- eventHub.$on('MRWidgetUpdateRequested', (cb) => {
+ eventHub.$on('MRWidgetUpdateRequested', cb => {
this.checkStatus(cb);
});
// `params` should be an Array contains a Boolean, like `[true]`
// Passing parameter as Boolean didn't work.
- eventHub.$on('SetBranchRemoveFlag', (params) => {
+ eventHub.$on('SetBranchRemoveFlag', params => {
[this.mr.isRemovingSourceBranch] = params;
});
- eventHub.$on('FailedToMerge', (mergeError) => {
+ eventHub.$on('FailedToMerge', mergeError => {
this.mr.state = 'failedToMerge';
this.mr.mergeError = mergeError;
});
- eventHub.$on('UpdateWidgetData', (data) => {
+ eventHub.$on('UpdateWidgetData', data => {
this.mr.setData(data);
});
@@ -243,10 +251,6 @@ export default {
this.stopPolling();
});
},
- handleMounted() {
- this.setFaviconHelper();
- this.initDeploymentsPolling();
- },
},
};
</script>
@@ -271,8 +275,10 @@ export default {
<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/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue
index 33af7a7f1df..690dd794ba4 100644
--- a/app/assets/javascripts/vue_shared/components/bar_chart.vue
+++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue
@@ -118,7 +118,9 @@ export default {
this.rectYAxisLabelDims.height != null ? this.rectYAxisLabelDims.height / 2 : 0;
const yCoord = this.vbHeight / 2 + rectWidth - 5;
- return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${this.yAxisTextRotation})`;
+ return `translate(${this.minX + this.yAxisTextTransformPadding}, ${yCoord}) rotate(-${
+ this.yAxisTextRotation
+ })`;
},
},
mounted() {
@@ -207,8 +209,7 @@ export default {
renderedYAxis.selectAll('.tick').each(function createTickLines(d, i) {
if (i > 0) {
- d3
- .select(this)
+ d3.select(this)
.select('line')
.attr('x2', width)
.attr('class', 'axis-tick');
@@ -217,8 +218,7 @@ export default {
// Add the panning capabilities
if (this.isPanAvailable) {
- d3
- .select(this.$refs.baseSvg)
+ d3.select(this.$refs.baseSvg)
.call(this.zoom)
.on('wheel.zoom', null); // This disables the pan of the graph with the scroll of the mouse wheel
}
diff --git a/app/assets/javascripts/vue_shared/components/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/deprecated_modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
index 9c1e5c68649..4d63296e332 100644
--- a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
@@ -1,85 +1,85 @@
<script>
- /* eslint-disable vue/require-default-prop */
- export default {
- name: 'DeprecatedModal', // use GlModal instead
+/* eslint-disable vue/require-default-prop */
+export default {
+ name: 'DeprecatedModal', // use GlModal instead
- props: {
- id: {
- type: String,
- required: false,
- },
- title: {
- type: String,
- required: false,
- },
- text: {
- type: String,
- required: false,
- },
- hideFooter: {
- type: Boolean,
- required: false,
- default: false,
- },
- kind: {
- type: String,
- required: false,
- default: 'primary',
- },
- modalDialogClass: {
- type: String,
- required: false,
- default: '',
- },
- closeKind: {
- type: String,
- required: false,
- default: 'default',
- },
- closeButtonLabel: {
- type: String,
- required: false,
- default: 'Cancel',
- },
- primaryButtonLabel: {
- type: String,
- required: false,
- default: '',
- },
- secondaryButtonLabel: {
- type: String,
- required: false,
- default: '',
- },
- submitDisabled: {
- type: Boolean,
- required: false,
- default: false,
- },
+ props: {
+ id: {
+ type: String,
+ required: false,
},
+ title: {
+ type: String,
+ required: false,
+ },
+ text: {
+ type: String,
+ required: false,
+ },
+ hideFooter: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ kind: {
+ type: String,
+ required: false,
+ default: 'primary',
+ },
+ modalDialogClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ closeKind: {
+ type: String,
+ required: false,
+ default: 'default',
+ },
+ closeButtonLabel: {
+ type: String,
+ required: false,
+ default: 'Cancel',
+ },
+ primaryButtonLabel: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ secondaryButtonLabel: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ submitDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
- computed: {
- btnKindClass() {
- return {
- [`btn-${this.kind}`]: true,
- };
- },
- btnCancelKindClass() {
- return {
- [`btn-${this.closeKind}`]: true,
- };
- },
+ computed: {
+ btnKindClass() {
+ return {
+ [`btn-${this.kind}`]: true,
+ };
+ },
+ btnCancelKindClass() {
+ return {
+ [`btn-${this.closeKind}`]: true,
+ };
},
+ },
- methods: {
- emitCancel(event) {
- this.$emit('cancel', event);
- },
- emitSubmit(event) {
- this.$emit('submit', event);
- },
+ methods: {
+ emitCancel(event) {
+ this.$emit('cancel', event);
+ },
+ emitSubmit(event) {
+ this.$emit('submit', event);
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js
index 9bca1993ccc..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',
@@ -583,7 +584,5 @@ const fileNameIcons = {
};
export default function getIconForFile(name) {
- return fileNameIcons[name] ||
- fileExtensionIcons[name ? name.split('.').pop() : ''] ||
- '';
+ return fileNameIcons[name] || fileExtensionIcons[name ? name.split('.').pop() : ''] || '';
}
diff --git a/app/assets/javascripts/vue_shared/components/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_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
index b023c5cfeb1..b5444d43ded 100644
--- a/app/assets/javascripts/vue_shared/components/gl_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -41,10 +41,14 @@ export default {
},
},
mounted() {
- $(this.$el).on('shown.bs.modal', this.opened).on('hidden.bs.modal', this.closed);
+ $(this.$el)
+ .on('shown.bs.modal', this.opened)
+ .on('hidden.bs.modal', this.closed);
},
beforeDestroy() {
- $(this.$el).off('shown.bs.modal', this.opened).off('hidden.bs.modal', this.closed);
+ $(this.$el)
+ .off('shown.bs.modal', this.opened)
+ .off('hidden.bs.modal', this.closed);
},
methods: {
emitCancel(event) {
@@ -103,7 +107,7 @@ export default {
<slot name="footer">
<button
type="button"
- class="btn js-modal-cancel-action"
+ class="btn js-modal-cancel-action qa-modal-cancel-button"
data-dismiss="modal"
@click="emitCancel($event)"
>
@@ -112,7 +116,7 @@ export default {
<button
:class="`btn-${footerPrimaryButtonVariant}`"
type="button"
- class="btn js-modal-primary-action"
+ class="btn js-modal-primary-action qa-modal-primary-button"
data-dismiss="modal"
@click="emitSubmit($event)"
>
diff --git a/app/assets/javascripts/vue_shared/components/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/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue
index 5e0e7315e99..26f9d5ddc91 100644
--- a/app/assets/javascripts/vue_shared/components/icon.vue
+++ b/app/assets/javascripts/vue_shared/components/icon.vue
@@ -1,5 +1,4 @@
<script>
-
// only allow classes in images.scss e.g. s12
const validSizes = [8, 10, 12, 16, 18, 24, 32, 48, 72];
let iconValidator = () => true;
@@ -105,6 +104,7 @@ export default {
:x="x"
:y="y"
:tabindex="tabIndex"
+ aria-hidden="true"
>
<use v-bind="{ 'xlink:href':spriteHref }"/>
</svg>
diff --git a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
index ca8ce554588..dc88749c18f 100644
--- a/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
+++ b/app/assets/javascripts/vue_shared/components/issue/issue_warning.vue
@@ -1,34 +1,34 @@
<script>
- import icon from '../../../vue_shared/components/icon.vue';
+import icon from '../../../vue_shared/components/icon.vue';
- export default {
- components: {
- icon,
+export default {
+ components: {
+ icon,
+ },
+ props: {
+ isLocked: {
+ type: Boolean,
+ default: false,
+ required: false,
},
- props: {
- isLocked: {
- type: Boolean,
- default: false,
- required: false,
- },
- isConfidential: {
- type: Boolean,
- default: false,
- required: false,
- },
+ isConfidential: {
+ type: Boolean,
+ default: false,
+ required: false,
},
- computed: {
- warningIcon() {
- if (this.isConfidential) return 'eye-slash';
- if (this.isLocked) return 'lock';
+ },
+ computed: {
+ warningIcon() {
+ if (this.isConfidential) return 'eye-slash';
+ if (this.isLocked) return 'lock';
- return '';
- },
- isLockedAndConfidential() {
- return this.isConfidential && this.isLocked;
- },
+ return '';
},
- };
+ isLockedAndConfidential() {
+ return this.isConfidential && this.isLocked;
+ },
+ },
+};
</script>
<template>
<div class="issuable-note-warning">
@@ -37,7 +37,6 @@
:name="warningIcon"
:size="16"
class="icon inline"
- aria-hidden="true"
/>
<span v-if="isLockedAndConfidential">
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
index 4cbd3e6429d..f9b7fd5b1f9 100644
--- a/app/assets/javascripts/vue_shared/components/loading_button.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -1,6 +1,6 @@
<script>
- /* eslint-disable vue/require-default-prop */
- /* This is a re-usable vue component for rendering a button
+/* eslint-disable vue/require-default-prop */
+/* This is a re-usable vue component for rendering a button
that will probably be sending off ajax requests and need
to show the loading status by setting the `loading` option.
This can also be used for initial page load when you don't
@@ -17,34 +17,34 @@
*/
- export default {
- props: {
- loading: {
- type: Boolean,
- required: false,
- default: false,
- },
- disabled: {
- type: Boolean,
- required: false,
- default: false,
- },
- label: {
- type: String,
- required: false,
- },
- containerClass: {
- type: [String, Array, Object],
- required: false,
- default: 'btn btn-align-content',
- },
+export default {
+ props: {
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- methods: {
- onClick(e) {
- this.$emit('click', e);
- },
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
},
- };
+ label: {
+ type: String,
+ required: false,
+ },
+ containerClass: {
+ type: [String, Array, Object],
+ required: false,
+ default: 'btn btn-align-content',
+ },
+ },
+ methods: {
+ onClick(e) {
+ this.$emit('click', e);
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 10e8ddad9cd..4687de62614 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -1,141 +1,141 @@
<script>
- import $ from 'jquery';
- import { s__ } from '~/locale';
- import Flash from '../../../flash';
- import GLForm from '../../../gl_form';
- import markdownHeader from './header.vue';
- import markdownToolbar from './toolbar.vue';
- import icon from '../icon.vue';
+import $ from 'jquery';
+import { s__ } from '~/locale';
+import Flash from '../../../flash';
+import GLForm from '../../../gl_form';
+import markdownHeader from './header.vue';
+import markdownToolbar from './toolbar.vue';
+import icon from '../icon.vue';
- export default {
- components: {
- markdownHeader,
- markdownToolbar,
- icon,
+export default {
+ components: {
+ markdownHeader,
+ markdownToolbar,
+ icon,
+ },
+ props: {
+ markdownPreviewPath: {
+ type: String,
+ required: false,
+ default: '',
},
- props: {
- markdownPreviewPath: {
- type: String,
- required: false,
- default: '',
- },
- markdownDocsPath: {
- type: String,
- required: true,
- },
- markdownVersion: {
- type: Number,
- required: false,
- default: 0,
- },
- addSpacingClasses: {
- type: Boolean,
- required: false,
- default: true,
- },
- quickActionsDocsPath: {
- type: String,
- required: false,
- default: '',
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
- enableAutocomplete: {
- type: Boolean,
- required: false,
- default: true,
- },
+ markdownDocsPath: {
+ type: String,
+ required: true,
},
- data() {
- return {
- markdownPreview: '',
- referencedCommands: '',
- referencedUsers: '',
- markdownPreviewLoading: false,
- previewMarkdown: false,
- };
+ markdownVersion: {
+ type: Number,
+ required: false,
+ default: 0,
},
- computed: {
- shouldShowReferencedUsers() {
- const referencedUsersThreshold = 10;
- return this.referencedUsers.length >= referencedUsersThreshold;
- },
+ addSpacingClasses: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- mounted() {
- /*
- GLForm class handles all the toolbar buttons
- */
- return new GLForm($(this.$refs['gl-form']), {
- emojis: this.enableAutocomplete,
- members: this.enableAutocomplete,
- issues: this.enableAutocomplete,
- mergeRequests: this.enableAutocomplete,
- epics: this.enableAutocomplete,
- milestones: this.enableAutocomplete,
- labels: this.enableAutocomplete,
- snippets: this.enableAutocomplete,
- });
+ quickActionsDocsPath: {
+ type: String,
+ required: false,
+ default: '',
},
- beforeDestroy() {
- const glForm = $(this.$refs['gl-form']).data('glForm');
- if (glForm) {
- glForm.destroy();
- }
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ enableAutocomplete: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- methods: {
- showPreviewTab() {
- if (this.previewMarkdown) return;
+ },
+ data() {
+ return {
+ markdownPreview: '',
+ referencedCommands: '',
+ referencedUsers: '',
+ markdownPreviewLoading: false,
+ previewMarkdown: false,
+ };
+ },
+ computed: {
+ shouldShowReferencedUsers() {
+ const referencedUsersThreshold = 10;
+ return this.referencedUsers.length >= referencedUsersThreshold;
+ },
+ },
+ mounted() {
+ /*
+ GLForm class handles all the toolbar buttons
+ */
+ return new GLForm($(this.$refs['gl-form']), {
+ emojis: this.enableAutocomplete,
+ members: this.enableAutocomplete,
+ issues: this.enableAutocomplete,
+ mergeRequests: this.enableAutocomplete,
+ epics: this.enableAutocomplete,
+ milestones: this.enableAutocomplete,
+ labels: this.enableAutocomplete,
+ snippets: this.enableAutocomplete,
+ });
+ },
+ beforeDestroy() {
+ const glForm = $(this.$refs['gl-form']).data('glForm');
+ if (glForm) {
+ glForm.destroy();
+ }
+ },
+ methods: {
+ showPreviewTab() {
+ if (this.previewMarkdown) return;
- this.previewMarkdown = true;
+ this.previewMarkdown = true;
- /*
+ /*
Can't use `$refs` as the component is technically in the parent component
so we access the VNode & then get the element
*/
- const text = this.$slots.textarea[0].elm.value;
+ const text = this.$slots.textarea[0].elm.value;
- if (text) {
- this.markdownPreviewLoading = true;
- this.$http
- .post(this.versionedPreviewPath(), { text })
- .then(resp => resp.json())
- .then(data => this.renderMarkdown(data))
- .catch(() => new Flash(s__('Error loading markdown preview')));
- } else {
- this.renderMarkdown();
- }
- },
+ if (text) {
+ this.markdownPreviewLoading = true;
+ this.$http
+ .post(this.versionedPreviewPath(), { text })
+ .then(resp => resp.json())
+ .then(data => this.renderMarkdown(data))
+ .catch(() => new Flash(s__('Error loading markdown preview')));
+ } else {
+ this.renderMarkdown();
+ }
+ },
- showWriteTab() {
- this.markdownPreview = '';
- this.previewMarkdown = false;
- },
+ showWriteTab() {
+ this.markdownPreview = '';
+ this.previewMarkdown = false;
+ },
- renderMarkdown(data = {}) {
- this.markdownPreviewLoading = false;
- this.markdownPreview = data.body || 'Nothing to preview.';
+ renderMarkdown(data = {}) {
+ this.markdownPreviewLoading = false;
+ this.markdownPreview = data.body || 'Nothing to preview.';
- if (data.references) {
- this.referencedCommands = data.references.commands;
- this.referencedUsers = data.references.users;
- }
+ if (data.references) {
+ this.referencedCommands = data.references.commands;
+ this.referencedUsers = data.references.users;
+ }
- this.$nextTick(() => {
- $(this.$refs['markdown-preview']).renderGFM();
- });
- },
+ this.$nextTick(() => {
+ $(this.$refs['markdown-preview']).renderGFM();
+ });
+ },
- versionedPreviewPath() {
- const { markdownPreviewPath, markdownVersion } = this;
- return `${markdownPreviewPath}${
- markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
- }markdown_version=${markdownVersion}`;
- },
+ versionedPreviewPath() {
+ const { markdownPreviewPath, markdownVersion } = this;
+ return `${markdownPreviewPath}${
+ markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
+ }markdown_version=${markdownVersion}`;
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index ccd53158820..3ddb39730c4 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -1,63 +1,64 @@
<script>
- import $ from 'jquery';
- import tooltip from '../../directives/tooltip';
- import toolbarButton from './toolbar_button.vue';
- import icon from '../icon.vue';
+import $ from 'jquery';
+import Tooltip from '../../directives/tooltip';
+import ToolbarButton from './toolbar_button.vue';
+import Icon from '../icon.vue';
- export default {
- directives: {
- tooltip,
+export default {
+ directives: {
+ Tooltip,
+ },
+ components: {
+ ToolbarButton,
+ Icon,
+ },
+ props: {
+ previewMarkdown: {
+ type: Boolean,
+ required: true,
},
- components: {
- toolbarButton,
- icon,
+ },
+ computed: {
+ mdTable() {
+ return [
+ '| header | header |',
+ '| ------ | ------ |',
+ '| cell | cell |',
+ '| cell | cell |',
+ ].join('\n');
},
- props: {
- previewMarkdown: {
- type: Boolean,
- required: true,
- },
+ },
+ mounted() {
+ $(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
+ $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
+ },
+ beforeDestroy() {
+ $(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
+ $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
+ },
+ methods: {
+ isValid(form) {
+ return (
+ !form ||
+ (form.find('.js-vue-markdown-field').length && $(this.$el).closest('form')[0] === form[0])
+ );
},
- computed: {
- mdTable() {
- return [
- '| header | header |',
- '| ------ | ------ |',
- '| cell | cell |',
- '| cell | cell |',
- ].join('\n');
- },
- },
- mounted() {
- $(document).on('markdown-preview:show.vue', this.previewMarkdownTab);
- $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab);
- },
- beforeDestroy() {
- $(document).off('markdown-preview:show.vue', this.previewMarkdownTab);
- $(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
- },
- methods: {
- isValid(form) {
- return !form ||
- form.find('.js-vue-markdown-field').length &&
- $(this.$el).closest('form')[0] === form[0];
- },
- previewMarkdownTab(event, form) {
- if (event.target.blur) event.target.blur();
- if (!this.isValid(form)) return;
+ previewMarkdownTab(event, form) {
+ if (event.target.blur) event.target.blur();
+ if (!this.isValid(form)) return;
- this.$emit('preview-markdown');
- },
+ this.$emit('preview-markdown');
+ },
- writeMarkdownTab(event, form) {
- if (event.target.blur) event.target.blur();
- if (!this.isValid(form)) return;
+ writeMarkdownTab(event, form) {
+ if (event.target.blur) event.target.blur();
+ if (!this.isValid(form)) return;
- this.$emit('write-markdown');
- },
+ this.$emit('write-markdown');
},
- };
+ },
+};
</script>
<template>
@@ -67,27 +68,27 @@
: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/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index c45dafa9807..feb7b8f227e 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -1,32 +1,32 @@
<script>
- import { Link } from '@gitlab-org/gitlab-ui';
+import { Link } from '@gitlab-org/gitlab-ui';
- export default {
- components: {
- 'gl-link': Link,
+export default {
+ components: {
+ 'gl-link': Link,
+ },
+ props: {
+ markdownDocsPath: {
+ type: String,
+ required: true,
},
- props: {
- markdownDocsPath: {
- type: String,
- required: true,
- },
- quickActionsDocsPath: {
- type: String,
- required: false,
- default: '',
- },
- canAttachFile: {
- type: Boolean,
- required: false,
- default: true,
- },
+ quickActionsDocsPath: {
+ type: String,
+ required: false,
+ default: '',
},
- computed: {
- hasQuickActionsDocsPath() {
- return this.quickActionsDocsPath !== '';
- },
+ canAttachFile: {
+ type: Boolean,
+ required: false,
+ default: true,
},
- };
+ },
+ computed: {
+ hasQuickActionsDocsPath() {
+ return this.quickActionsDocsPath !== '';
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
index bda33636369..3e89e1c1e75 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar_button.vue
@@ -1,44 +1,44 @@
<script>
- import tooltip from '../../directives/tooltip';
- import icon from '../icon.vue';
+import tooltip from '../../directives/tooltip';
+import icon from '../icon.vue';
- export default {
- components: {
- icon,
+export default {
+ components: {
+ icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ buttonTitle: {
+ type: String,
+ required: true,
},
- directives: {
- tooltip,
+ icon: {
+ type: String,
+ required: true,
},
- props: {
- buttonTitle: {
- type: String,
- required: true,
- },
- icon: {
- type: String,
- required: true,
- },
- tag: {
- type: String,
- required: true,
- },
- tagBlock: {
- type: String,
- required: false,
- default: '',
- },
- tagSelect: {
- type: String,
- required: false,
- default: '',
- },
- prepend: {
- type: Boolean,
- required: false,
- default: false,
- },
+ tag: {
+ type: String,
+ required: true,
},
- };
+ tagBlock: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ tagSelect: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ prepend: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/memory_graph.vue b/app/assets/javascripts/vue_shared/components/memory_graph.vue
index 552a92541be..964dedb38c4 100644
--- a/app/assets/javascripts/vue_shared/components/memory_graph.vue
+++ b/app/assets/javascripts/vue_shared/components/memory_graph.vue
@@ -41,7 +41,8 @@ export default {
// Find metric timestamp which is closest to deploymentTime
timestampDiff = Math.abs(metricTimestamps[0] - median);
metricTimestamps.forEach((timestamp, index) => {
- if (index === 0) { // Skip first element
+ if (index === 0) {
+ // Skip first element
return;
}
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
index 38115f268bb..dcad79e521d 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue
@@ -1,41 +1,39 @@
<script>
- /**
- * Common component to render a placeholder note and user information.
- *
- * This component needs to be used with a vuex store.
- * That vuex store needs to have a `getUserData` getter that contains
- * {
- * path: String,
- * avatar_url: String,
- * name: String,
- * username: String,
- * }
- *
- * @example
- * <placeholder-note
- * :note="{body: 'This is a note'}"
- * />
- */
- import { mapGetters } from 'vuex';
- import userAvatarLink from '../user_avatar/user_avatar_link.vue';
+/**
+ * Common component to render a placeholder note and user information.
+ *
+ * This component needs to be used with a vuex store.
+ * That vuex store needs to have a `getUserData` getter that contains
+ * {
+ * path: String,
+ * avatar_url: String,
+ * name: String,
+ * username: String,
+ * }
+ *
+ * @example
+ * <placeholder-note
+ * :note="{body: 'This is a note'}"
+ * />
+ */
+import { mapGetters } from 'vuex';
+import userAvatarLink from '../user_avatar/user_avatar_link.vue';
- export default {
- name: 'PlaceholderNote',
- components: {
- userAvatarLink,
+export default {
+ name: 'PlaceholderNote',
+ components: {
+ userAvatarLink,
+ },
+ props: {
+ note: {
+ type: Object,
+ required: true,
},
- props: {
- note: {
- type: Object,
- required: true,
- },
- },
- computed: {
- ...mapGetters([
- 'getUserData',
- ]),
- },
- };
+ },
+ computed: {
+ ...mapGetters(['getUserData']),
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue
index 95e2b38e292..674f923478d 100644
--- a/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_system_note.vue
@@ -1,21 +1,21 @@
<script>
- /**
- * Common component to render a placeholder system note.
- *
- * @example
- * <placeholder-system-note
- * :note="{ body: 'Commands are being applied'}"
- * />
- */
- export default {
- name: 'PlaceholderSystemNote',
- props: {
- note: {
- type: Object,
- required: true,
- },
+/**
+ * Common component to render a placeholder system note.
+ *
+ * @example
+ * <placeholder-system-note
+ * :note="{ body: 'Commands are being applied'}"
+ * />
+ */
+export default {
+ name: 'PlaceholderSystemNote',
+ props: {
+ note: {
+ type: Object,
+ required: true,
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 2122d0a508e..de3c7a80365 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -109,7 +109,7 @@ export default {
class="system-note-commit-list-toggler flex-row"
@click="expanded = !expanded"
>
- <Icon
+ <icon
:name="toggleIcon"
:size="8"
class="append-right-5"
diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
index 7947ae1e4da..bf736a378dd 100644
--- a/app/assets/javascripts/vue_shared/components/panel_resizer.vue
+++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue
@@ -1,90 +1,90 @@
<script>
- export default {
- props: {
- startSize: {
- type: Number,
- required: true,
- },
- side: {
- type: String,
- required: true,
- },
- minSize: {
- type: Number,
- required: false,
- default: 0,
- },
- maxSize: {
- type: Number,
- required: false,
- default: Number.MAX_VALUE,
- },
- enabled: {
- type: Boolean,
- required: false,
- default: true,
- },
+export default {
+ props: {
+ startSize: {
+ type: Number,
+ required: true,
},
- data() {
- return {
- size: this.startSize,
- };
+ side: {
+ type: String,
+ required: true,
},
- computed: {
- className() {
- return `drag-${this.side}`;
- },
- cursorStyle() {
- if (this.enabled) {
- return { cursor: 'ew-resize' };
- }
- return {};
- },
+ minSize: {
+ type: Number,
+ required: false,
+ default: 0,
},
- methods: {
- resetSize(e) {
- e.preventDefault();
- this.$emit('resize-start', this.size);
+ maxSize: {
+ type: Number,
+ required: false,
+ default: Number.MAX_VALUE,
+ },
+ enabled: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ },
+ data() {
+ return {
+ size: this.startSize,
+ };
+ },
+ computed: {
+ className() {
+ return `drag-${this.side}`;
+ },
+ cursorStyle() {
+ if (this.enabled) {
+ return { cursor: 'ew-resize' };
+ }
+ return {};
+ },
+ },
+ methods: {
+ resetSize(e) {
+ e.preventDefault();
+ this.$emit('resize-start', this.size);
- this.size = this.startSize;
- this.$emit('update:size', this.size);
+ this.size = this.startSize;
+ this.$emit('update:size', this.size);
- // End resizing on next tick so that listeners can react to DOM changes
- this.$nextTick(() => {
- this.$emit('resize-end', this.size);
- });
- },
- startDrag(e) {
- if (this.enabled) {
- e.preventDefault();
- this.startPos = e.clientX;
- this.currentStartSize = this.size;
- document.addEventListener('mousemove', this.drag);
- document.addEventListener('mouseup', this.endDrag, { once: true });
- this.$emit('resize-start', this.size);
- }
- },
- drag(e) {
+ // End resizing on next tick so that listeners can react to DOM changes
+ this.$nextTick(() => {
+ this.$emit('resize-end', this.size);
+ });
+ },
+ startDrag(e) {
+ if (this.enabled) {
e.preventDefault();
- let moved = e.clientX - this.startPos;
- if (this.side === 'left') moved = -moved;
- let newSize = this.currentStartSize + moved;
- if (newSize < this.minSize) {
- newSize = this.minSize;
- } else if (newSize > this.maxSize) {
- newSize = this.maxSize;
- }
- this.size = newSize;
+ this.startPos = e.clientX;
+ this.currentStartSize = this.size;
+ document.addEventListener('mousemove', this.drag);
+ document.addEventListener('mouseup', this.endDrag, { once: true });
+ this.$emit('resize-start', this.size);
+ }
+ },
+ drag(e) {
+ e.preventDefault();
+ let moved = e.clientX - this.startPos;
+ if (this.side === 'left') moved = -moved;
+ let newSize = this.currentStartSize + moved;
+ if (newSize < this.minSize) {
+ newSize = this.minSize;
+ } else if (newSize > this.maxSize) {
+ newSize = this.maxSize;
+ }
+ this.size = newSize;
- this.$emit('update:size', newSize);
- },
- endDrag(e) {
- e.preventDefault();
- document.removeEventListener('mousemove', this.drag);
- this.$emit('resize-end', this.size);
- },
+ this.$emit('update:size', newSize);
+ },
+ endDrag(e) {
+ e.preventDefault();
+ document.removeEventListener('mousemove', this.drag);
+ this.$emit('resize-end', this.size);
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue
index bfeece12077..782d8e3abf6 100644
--- a/app/assets/javascripts/vue_shared/components/pikaday.vue
+++ b/app/assets/javascripts/vue_shared/components/pikaday.vue
@@ -1,62 +1,62 @@
<script>
- import Pikaday from 'pikaday';
- import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix';
+import Pikaday from 'pikaday';
+import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix';
- export default {
- name: 'DatePicker',
- props: {
- label: {
- type: String,
- required: false,
- default: 'Date picker',
- },
- selectedDate: {
- type: Date,
- required: false,
- default: null,
- },
- minDate: {
- type: Date,
- required: false,
- default: null,
- },
- maxDate: {
- type: Date,
- required: false,
- default: null,
- },
+export default {
+ name: 'DatePicker',
+ props: {
+ label: {
+ type: String,
+ required: false,
+ default: 'Date picker',
},
- mounted() {
- this.calendar = new Pikaday({
- field: this.$el.querySelector('.dropdown-menu-toggle'),
- theme: 'gitlab-theme animate-picker',
- format: 'yyyy-mm-dd',
- container: this.$el,
- defaultDate: this.selectedDate,
- setDefaultDate: !!this.selectedDate,
- minDate: this.minDate,
- maxDate: this.maxDate,
- parse: dateString => parsePikadayDate(dateString),
- toString: date => pikadayToString(date),
- onSelect: this.selected.bind(this),
- onClose: this.toggled.bind(this),
- });
-
- this.$el.append(this.calendar.el);
- this.calendar.show();
+ selectedDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ minDate: {
+ type: Date,
+ required: false,
+ default: null,
},
- beforeDestroy() {
- this.calendar.destroy();
+ maxDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ },
+ mounted() {
+ this.calendar = new Pikaday({
+ field: this.$el.querySelector('.dropdown-menu-toggle'),
+ theme: 'gitlab-theme animate-picker',
+ format: 'yyyy-mm-dd',
+ container: this.$el,
+ defaultDate: this.selectedDate,
+ setDefaultDate: !!this.selectedDate,
+ minDate: this.minDate,
+ maxDate: this.maxDate,
+ parse: dateString => parsePikadayDate(dateString),
+ toString: date => pikadayToString(date),
+ onSelect: this.selected.bind(this),
+ onClose: this.toggled.bind(this),
+ });
+
+ this.$el.append(this.calendar.el);
+ this.calendar.show();
+ },
+ beforeDestroy() {
+ this.calendar.destroy();
+ },
+ methods: {
+ selected(dateText) {
+ this.$emit('newDateSelected', this.calendar.toString(dateText));
},
- methods: {
- selected(dateText) {
- this.$emit('newDateSelected', this.calendar.toString(dateText));
- },
- toggled() {
- this.$emit('hidePicker');
- },
+ toggled() {
+ this.$emit('hidePicker');
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
index 97ca4d93bd7..1a2fd2ad985 100644
--- a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
+++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue
@@ -1,6 +1,5 @@
<script>
-
- /* This is a re-usable vue component for rendering a project avatar that
+/* This is a re-usable vue component for rendering a project avatar that
does not need to link to the project's profile. The image and an optional
tooltip can be configured by props passed to this component.
@@ -16,70 +15,70 @@
*/
- import defaultAvatarUrl from 'images/no_avatar.png';
- import { placeholderImage } from '../../../lazy_loader';
- import tooltip from '../../directives/tooltip';
+import defaultAvatarUrl from 'images/no_avatar.png';
+import { placeholderImage } from '../../../lazy_loader';
+import tooltip from '../../directives/tooltip';
- export default {
- name: 'ProjectAvatarImage',
- directives: {
- tooltip,
+export default {
+ name: 'ProjectAvatarImage',
+ directives: {
+ tooltip,
+ },
+ props: {
+ lazy: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ imgSrc: {
+ type: String,
+ required: false,
+ default: defaultAvatarUrl,
+ },
+ cssClasses: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ imgAlt: {
+ type: String,
+ required: false,
+ default: 'project avatar',
+ },
+ size: {
+ type: Number,
+ required: false,
+ default: 20,
+ },
+ tooltipText: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ tooltipPlacement: {
+ type: String,
+ required: false,
+ default: 'top',
+ },
+ },
+ computed: {
+ // API response sends null when gravatar is disabled and
+ // we provide an empty string when we use it inside project avatar link.
+ // In both cases we should render the defaultAvatarUrl
+ sanitizedSource() {
+ return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
+ },
+ resultantSrcAttribute() {
+ return this.lazy ? placeholderImage : this.sanitizedSource;
},
- props: {
- lazy: {
- type: Boolean,
- required: false,
- default: false,
- },
- imgSrc: {
- type: String,
- required: false,
- default: defaultAvatarUrl,
- },
- cssClasses: {
- type: String,
- required: false,
- default: '',
- },
- imgAlt: {
- type: String,
- required: false,
- default: 'project avatar',
- },
- size: {
- type: Number,
- required: false,
- default: 20,
- },
- tooltipText: {
- type: String,
- required: false,
- default: '',
- },
- tooltipPlacement: {
- type: String,
- required: false,
- default: 'top',
- },
+ tooltipContainer() {
+ return this.tooltipText ? 'body' : null;
},
- computed: {
- // API response sends null when gravatar is disabled and
- // we provide an empty string when we use it inside project avatar link.
- // In both cases we should render the defaultAvatarUrl
- sanitizedSource() {
- return this.imgSrc === '' || this.imgSrc === null ? defaultAvatarUrl : this.imgSrc;
- },
- resultantSrcAttribute() {
- return this.lazy ? placeholderImage : this.sanitizedSource;
- },
- tooltipContainer() {
- return this.tooltipText ? 'body' : null;
- },
- avatarSizeClass() {
- return `s${this.size}`;
- },
+ avatarSizeClass() {
+ return `s${this.size}`;
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
index a2a9a5e6987..09394847b10 100644
--- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -1,67 +1,67 @@
<script>
- import DeprecatedModal from './deprecated_modal.vue';
+import DeprecatedModal from './deprecated_modal.vue';
- export default {
- name: 'RecaptchaModal',
+export default {
+ name: 'RecaptchaModal',
- components: {
- DeprecatedModal,
- },
+ components: {
+ DeprecatedModal,
+ },
- props: {
- html: {
- type: String,
- required: false,
- default: '',
- },
+ props: {
+ html: {
+ type: String,
+ required: false,
+ default: '',
},
+ },
- data() {
- return {
- script: {},
- scriptSrc: 'https://www.google.com/recaptcha/api.js',
- };
- },
+ data() {
+ return {
+ script: {},
+ scriptSrc: 'https://www.google.com/recaptcha/api.js',
+ };
+ },
- watch: {
- html() {
- this.appendRecaptchaScript();
- },
+ watch: {
+ html() {
+ this.appendRecaptchaScript();
},
+ },
- mounted() {
- window.recaptchaDialogCallback = this.submit.bind(this);
- },
+ mounted() {
+ window.recaptchaDialogCallback = this.submit.bind(this);
+ },
- methods: {
- appendRecaptchaScript() {
- this.removeRecaptchaScript();
+ methods: {
+ appendRecaptchaScript() {
+ this.removeRecaptchaScript();
- const script = document.createElement('script');
- script.src = this.scriptSrc;
- script.classList.add('js-recaptcha-script');
- script.async = true;
- script.defer = true;
+ const script = document.createElement('script');
+ script.src = this.scriptSrc;
+ script.classList.add('js-recaptcha-script');
+ script.async = true;
+ script.defer = true;
- this.script = script;
+ this.script = script;
- document.body.appendChild(script);
- },
+ document.body.appendChild(script);
+ },
- removeRecaptchaScript() {
- if (this.script instanceof Element) this.script.remove();
- },
+ removeRecaptchaScript() {
+ if (this.script instanceof Element) this.script.remove();
+ },
- close() {
- this.removeRecaptchaScript();
- this.$emit('close');
- },
+ close() {
+ this.removeRecaptchaScript();
+ this.$emit('close');
+ },
- submit() {
- this.$el.querySelector('form').submit();
- },
+ submit() {
+ this.$el.querySelector('form').submit();
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
index 9d757b27edc..500586302cf 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
@@ -1,96 +1,96 @@
<script>
- import datePicker from '../pikaday.vue';
- import toggleSidebar from './toggle_sidebar.vue';
- import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
- import { dateInWords } from '../../../lib/utils/datetime_utility';
+import datePicker from '../pikaday.vue';
+import toggleSidebar from './toggle_sidebar.vue';
+import collapsedCalendarIcon from './collapsed_calendar_icon.vue';
+import { dateInWords } from '../../../lib/utils/datetime_utility';
- export default {
- name: 'SidebarDatePicker',
- components: {
- datePicker,
- toggleSidebar,
- collapsedCalendarIcon,
- },
- props: {
- blockClass: {
- type: String,
- required: false,
- default: '',
- },
- collapsed: {
- type: Boolean,
- required: false,
- default: true,
- },
- showToggleSidebar: {
- type: Boolean,
- required: false,
- default: false,
- },
- isLoading: {
- type: Boolean,
- required: false,
- default: false,
- },
- editable: {
- type: Boolean,
- required: false,
- default: false,
- },
- label: {
- type: String,
- required: false,
- default: 'Date picker',
- },
- selectedDate: {
- type: Date,
- required: false,
- default: null,
- },
- minDate: {
- type: Date,
- required: false,
- default: null,
- },
- maxDate: {
- type: Date,
- required: false,
- default: null,
- },
- },
- data() {
- return {
- editing: false,
- };
- },
- computed: {
- selectedAndEditable() {
- return this.selectedDate && this.editable;
- },
- selectedDateWords() {
- return dateInWords(this.selectedDate, true);
- },
- collapsedText() {
- return this.selectedDateWords ? this.selectedDateWords : 'None';
- },
- },
- methods: {
- stopEditing() {
- this.editing = false;
- },
- toggleDatePicker() {
- this.editing = !this.editing;
- },
- newDateSelected(date = null) {
- this.date = date;
- this.editing = false;
- this.$emit('saveDate', date);
- },
- toggleSidebar() {
- this.$emit('toggleCollapse');
- },
- },
- };
+export default {
+ name: 'SidebarDatePicker',
+ components: {
+ datePicker,
+ toggleSidebar,
+ collapsedCalendarIcon,
+ },
+ props: {
+ blockClass: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ collapsed: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ showToggleSidebar: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ editable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ label: {
+ type: String,
+ required: false,
+ default: 'Date picker',
+ },
+ selectedDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ minDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ maxDate: {
+ type: Date,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ editing: false,
+ };
+ },
+ computed: {
+ selectedAndEditable() {
+ return this.selectedDate && this.editable;
+ },
+ selectedDateWords() {
+ return dateInWords(this.selectedDate, true);
+ },
+ collapsedText() {
+ return this.selectedDateWords ? this.selectedDateWords : 'None';
+ },
+ },
+ methods: {
+ stopEditing() {
+ this.editing = false;
+ },
+ toggleDatePicker() {
+ this.editing = !this.editing;
+ },
+ newDateSelected(date = null) {
+ this.date = date;
+ this.editing = false;
+ this.$emit('saveDate', date);
+ },
+ toggleSidebar() {
+ this.$emit('toggleCollapse');
+ },
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 8e9621c956f..03a5a078879 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -1,17 +1,17 @@
<script>
- import { s__ } from '../../locale';
-
- const PAGINATION_UI_BUTTON_LIMIT = 4;
- const UI_LIMIT = 6;
- const SPREAD = '...';
- const PREV = s__('Pagination|Prev');
- const NEXT = s__('Pagination|Next');
- const FIRST = s__('Pagination|« First');
- const LAST = s__('Pagination|Last »');
-
- export default {
- props: {
- /**
+import { s__ } from '../../locale';
+
+const PAGINATION_UI_BUTTON_LIMIT = 4;
+const UI_LIMIT = 6;
+const SPREAD = '...';
+const PREV = s__('Pagination|Prev');
+const NEXT = s__('Pagination|Next');
+const FIRST = s__('Pagination|« First');
+const LAST = s__('Pagination|Last »');
+
+export default {
+ props: {
+ /**
This function will take the information given by the pagination component
Here is an example `change` method:
@@ -20,12 +20,12 @@
gl.utils.visitUrl(`?page=${pagenum}`);
},
*/
- change: {
- type: Function,
- required: true,
- },
+ change: {
+ type: Function,
+ required: true,
+ },
- /**
+ /**
pageInfo will come from the headers of the API call
in the `.then` clause of the VueResource API call
there should be a function that contructs the pageInfo for this component
@@ -41,94 +41,94 @@
previousPage: +headers['X-Prev-Page'],
});
*/
- pageInfo: {
- type: Object,
- required: true,
- },
+ pageInfo: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ prev() {
+ return this.pageInfo.previousPage;
+ },
+ next() {
+ return this.pageInfo.nextPage;
+ },
+ getItems() {
+ const total = this.pageInfo.totalPages;
+ const { page } = this.pageInfo;
+ const items = [];
+
+ if (page > 1) {
+ items.push({ title: FIRST, first: true });
+ }
+
+ if (page > 1) {
+ items.push({ title: PREV, prev: true });
+ } else {
+ items.push({ title: PREV, disabled: true, prev: true });
+ }
+
+ if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
+
+ const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
+ const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
+
+ for (let i = start; i <= end; i += 1) {
+ const isActive = i === page;
+ items.push({ title: i, active: isActive, page: true });
+ }
+
+ if (total - page > PAGINATION_UI_BUTTON_LIMIT) {
+ items.push({ title: SPREAD, separator: true, page: true });
+ }
+
+ if (page === total) {
+ items.push({ title: NEXT, disabled: true, next: true });
+ } else if (total - page >= 1) {
+ items.push({ title: NEXT, next: true });
+ }
+
+ if (total - page >= 1) {
+ items.push({ title: LAST, last: true });
+ }
+
+ return items;
+ },
+ showPagination() {
+ return this.pageInfo.totalPages > 1;
},
- computed: {
- prev() {
- return this.pageInfo.previousPage;
- },
- next() {
- return this.pageInfo.nextPage;
- },
- getItems() {
- const total = this.pageInfo.totalPages;
- const { page } = this.pageInfo;
- const items = [];
-
- if (page > 1) {
- items.push({ title: FIRST, first: true });
- }
-
- if (page > 1) {
- items.push({ title: PREV, prev: true });
- } else {
- items.push({ title: PREV, disabled: true, prev: true });
- }
-
- if (page > UI_LIMIT) items.push({ title: SPREAD, separator: true });
-
- const start = Math.max(page - PAGINATION_UI_BUTTON_LIMIT, 1);
- const end = Math.min(page + PAGINATION_UI_BUTTON_LIMIT, total);
-
- for (let i = start; i <= end; i += 1) {
- const isActive = i === page;
- items.push({ title: i, active: isActive, page: true });
- }
-
- if (total - page > PAGINATION_UI_BUTTON_LIMIT) {
- items.push({ title: SPREAD, separator: true, page: true });
- }
-
- if (page === total) {
- items.push({ title: NEXT, disabled: true, next: true });
- } else if (total - page >= 1) {
- items.push({ title: NEXT, next: true });
- }
-
- if (total - page >= 1) {
- items.push({ title: LAST, last: true });
- }
-
- return items;
- },
- showPagination() {
- return this.pageInfo.totalPages > 1;
- },
+ },
+ methods: {
+ changePage(text, isDisabled) {
+ if (isDisabled) return;
+
+ const { totalPages, nextPage, previousPage } = this.pageInfo;
+
+ switch (text) {
+ case SPREAD:
+ break;
+ case LAST:
+ this.change(totalPages);
+ break;
+ case NEXT:
+ this.change(nextPage);
+ break;
+ case PREV:
+ this.change(previousPage);
+ break;
+ case FIRST:
+ this.change(1);
+ break;
+ default:
+ this.change(+text);
+ break;
+ }
},
- methods: {
- changePage(text, isDisabled) {
- if (isDisabled) return;
-
- const { totalPages, nextPage, previousPage } = this.pageInfo;
-
- switch (text) {
- case SPREAD:
- break;
- case LAST:
- this.change(totalPages);
- break;
- case NEXT:
- this.change(nextPage);
- break;
- case PREV:
- this.change(previousPage);
- break;
- case FIRST:
- this.change(1);
- break;
- default:
- this.change(+text);
- break;
- }
- },
- hideOnSmallScreen(item) {
- return !item.first && !item.last && !item.next && !item.prev && !item.active;
- },
+ hideOnSmallScreen(item) {
+ return !item.first && !item.last && !item.next && !item.prev && !item.active;
},
- };
+ },
+};
</script>
<template>
<div
diff --git a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
index 368eeb6c453..d760263929a 100644
--- a/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
+++ b/app/assets/javascripts/vue_shared/components/time_ago_tooltip.vue
@@ -11,9 +11,7 @@ export default {
directives: {
tooltip,
},
- mixins: [
- timeagoMixin,
- ],
+ mixins: [timeagoMixin],
props: {
time: {
type: String,
diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue
index 5b9c51786d6..4e9289cbed8 100644
--- a/app/assets/javascripts/vue_shared/components/toggle_button.vue
+++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue
@@ -1,60 +1,60 @@
<script>
- import { s__ } from '../../locale';
- import icon from './icon.vue';
+import { s__ } from '../../locale';
+import icon from './icon.vue';
- const ICON_ON = 'status_success_borderless';
- const ICON_OFF = 'status_failed_borderless';
- const LABEL_ON = s__('ToggleButton|Toggle Status: ON');
- const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF');
+const ICON_ON = 'status_success_borderless';
+const ICON_OFF = 'status_failed_borderless';
+const LABEL_ON = s__('ToggleButton|Toggle Status: ON');
+const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF');
- export default {
- components: {
- icon,
- },
+export default {
+ components: {
+ icon,
+ },
- model: {
- prop: 'value',
- event: 'change',
- },
+ model: {
+ prop: 'value',
+ event: 'change',
+ },
- props: {
- name: {
- type: String,
- required: false,
- default: null,
- },
- value: {
- type: Boolean,
- required: false,
- default: null,
- },
- disabledInput: {
- type: Boolean,
- required: false,
- default: false,
- },
- isLoading: {
- type: Boolean,
- required: false,
- default: false,
- },
+ props: {
+ name: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ value: {
+ type: Boolean,
+ required: false,
+ default: null,
},
+ disabledInput: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
- computed: {
- toggleIcon() {
- return this.value ? ICON_ON : ICON_OFF;
- },
- ariaLabel() {
- return this.value ? LABEL_ON : LABEL_OFF;
- },
+ computed: {
+ toggleIcon() {
+ return this.value ? ICON_ON : ICON_OFF;
+ },
+ ariaLabel() {
+ return this.value ? LABEL_ON : LABEL_OFF;
},
+ },
- methods: {
- toggleFeature() {
- if (!this.disabledInput) this.$emit('change', !this.value);
- },
+ methods: {
+ toggleFeature() {
+ if (!this.disabledInput) this.$emit('change', !this.value);
},
- };
+ },
+};
</script>
<template>
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
index ee3157bcb1b..14cb44b8619 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue
@@ -1,5 +1,4 @@
<script>
-
/* This is a re-usable vue component for rendering a user avatar wrapped in
a clickable link (likely to the user's profile). The link, image, and
tooltip can be configured by props passed to this component.
diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
index ef3b16edf5f..8e460566d09 100644
--- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
+++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue
@@ -1,5 +1,4 @@
<script>
-
/* This is a re-usable vue component for rendering a user avatar svg (typically
for a blank state). It will receive styles comparable to the user avatar,
but no image is loaded, it isn't wrapped in a link, and tooltips aren't supported.
@@ -42,4 +41,3 @@ export default {
v-html="svg"
/>
</template>
-
diff --git a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
index f94cc670edf..67a1632269e 100644
--- a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
+++ b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js
@@ -4,10 +4,7 @@
*
* Components need to have `scope`, `page` and `requestData`
*/
-import {
- historyPushState,
- buildUrlWithCurrentLocation,
-} from '../../lib/utils/common_utils';
+import { historyPushState, buildUrlWithCurrentLocation } from '../../lib/utils/common_utils';
export default {
methods: {
@@ -24,12 +21,14 @@ export default {
// stop polling
this.poll.stop();
- const queryString = Object.keys(parameters).map((parameter) => {
- const value = parameters[parameter];
- // update internal state for UI
- this[parameter] = value;
- return `${parameter}=${encodeURIComponent(value)}`;
- }).join('&');
+ const queryString = Object.keys(parameters)
+ .map(parameter => {
+ const value = parameters[parameter];
+ // update internal state for UI
+ this[parameter] = value;
+ return `${parameter}=${encodeURIComponent(value)}`;
+ })
+ .join('&');
// update polling parameters
this.requestData = parameters;
diff --git a/app/assets/javascripts/vue_shared/models/label.js b/app/assets/javascripts/vue_shared/models/label.js
index d29c7fe973a..2d2732d0661 100644
--- a/app/assets/javascripts/vue_shared/models/label.js
+++ b/app/assets/javascripts/vue_shared/models/label.js
@@ -6,7 +6,7 @@ export default class ListLabel {
this.color = obj.color;
this.textColor = obj.text_color;
this.description = obj.description;
- this.priority = (obj.priority !== null) ? obj.priority : Infinity;
+ this.priority = obj.priority !== null ? obj.priority : Infinity;
}
}
diff --git a/app/assets/javascripts/vue_shared/translate.js b/app/assets/javascripts/vue_shared/translate.js
index 48c63373b77..e0baf03acc3 100644
--- a/app/assets/javascripts/vue_shared/translate.js
+++ b/app/assets/javascripts/vue_shared/translate.js
@@ -1,11 +1,6 @@
-import {
- __,
- n__,
- s__,
- sprintf,
-} from '../locale';
+import { __, n__, s__, sprintf } from '../locale';
-export default (Vue) => {
+export default Vue => {
Vue.mixin({
methods: {
/**
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
index b9693892f45..754025207c8 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
@@ -21,7 +21,7 @@ Vue.http.interceptors.push((request, next) => {
Vue.http.interceptors.push((request, next) => {
request.headers.set(csrf.headerKey, csrf.token);
- next((response) => {
+ next(response => {
// Headers object has a `forEach` property that iterates through all values.
const headers = {};
diff --git a/app/assets/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/application.scss b/app/assets/stylesheets/application.scss
index ffe65ce780e..bd1cca69c03 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -17,7 +17,7 @@
*/
@import "../../../node_modules/pikaday/scss/pikaday";
-@import "../../../node_modules/dropzone/dist/basic.css";
+@import "../../../node_modules/dropzone/dist/basic";
/*
* GitLab UI framework
diff --git a/app/assets/stylesheets/framework.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/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 7145a76db6d..f26b1fddae5 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -283,18 +283,20 @@
.dismiss-button {
position: absolute;
- right: 6px;
- top: 6px;
+ right: $gl-padding-8;
+ top: $gl-padding-8;
cursor: pointer;
- color: $blue-300;
+ color: $blue-500;
z-index: 1;
border: 0;
background-color: transparent;
+ padding: $gl-padding-8;
+ line-height: 0;
&:hover,
&:focus {
border: 0;
- color: $blue-400;
+ color: $blue-700;
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 8603714f709..cdfad30e7ca 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -144,6 +144,13 @@
top: 11px;
right: 8px;
}
+
+ .ic-chevron-down {
+ position: absolute;
+ top: $gl-padding-8;
+ right: $gl-padding-8;
+ color: $gray-darkest;
+ }
}
@mixin dropdown-item-hover {
@@ -561,6 +568,10 @@
top: -1px;
}
+.dropdown-menu-close-icon {
+ vertical-align: middle;
+}
+
.dropdown-menu-back {
left: 7px;
top: 2px;
@@ -572,9 +583,10 @@
padding: 0 10px;
.fa,
- .input-icon {
+ .input-icon,
+ .ic-search {
position: absolute;
- top: 10px;
+ top: $gl-padding-8;
right: 20px;
color: $dropdown-input-fa-color;
font-size: 12px;
diff --git a/app/assets/stylesheets/framework/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/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index be41dbfc61f..1c84baf68ed 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -12,6 +12,15 @@
max-width: $max-width;
}
+/**
+ * Mixin for fixed width container
+ */
+@mixin fixed-width-container {
+ max-width: $limited-layout-width - ($gl-padding * 2);
+ margin-left: auto;
+ margin-right: auto;
+}
+
/*
* Mixin for markdown tables
*/
diff --git a/app/assets/stylesheets/framework/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/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index 8bab8cf36b1..bf6f66d30ff 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,12 @@
}
}
+ button {
+ padding-top: 0;
+ }
+
&.active a,
+ &.active button,
a.active {
color: $black;
font-weight: $gl-font-weight-bold;
@@ -42,6 +49,10 @@
color: $black;
}
}
+
+ &.md-header-tab button {
+ line-height: 19px;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b7a95f604b8..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;
@@ -605,6 +606,7 @@ $perf-bar-development: #4c1210;
$perf-bar-bucket-bg: #111;
$perf-bar-bucket-box-shadow-from: rgba($white-light, 0.2);
$perf-bar-bucket-box-shadow-to: rgba($black, 0.25);
+$perf-bar-canary-text: $orange-400;
/*
Issuable warning
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index b3c5c693824..54fbd40cece 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -305,8 +305,7 @@
}
.confidential-icon {
- position: relative;
- top: 1px;
+ vertical-align: text-top;
margin-right: 5px;
}
}
diff --git a/app/assets/stylesheets/pages/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/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index f0228768b5a..ec2108b15be 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -21,7 +21,7 @@
}
}
- svg {
+ .svg-container svg {
width: 136px;
height: 136px;
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 17b02c6e31e..8d884ad6891 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1013,6 +1013,7 @@
.with-performance-bar & {
top: 135px;
+ max-height: calc(100vh - 135px);
}
}
@@ -1046,3 +1047,19 @@
left: auto;
line-height: 0;
}
+
+@media (max-width: map-get($grid-breakpoints, md)-1) {
+ .diffs .files {
+ @include fixed-width-container;
+ flex-direction: column;
+
+ .diff-tree-list {
+ width: 100%;
+ }
+
+ .tree-list-holder {
+ max-height: calc(50px + 50vh);
+ padding-right: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 676438969b5..347fcad771a 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -85,6 +85,7 @@
margin-right: 3px;
color: $gl-text-color-secondary;
display: inline-block;
+ vertical-align: text-top;
.fa:nth-child(1) {
margin-right: 3px;
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index fe792a53b44..f0cb81e0bc3 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -191,7 +191,7 @@
}
}
- svg {
+ .svg-container svg {
width: 62px;
height: 50px;
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 62a9f97caa9..00b06aea898 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -1,8 +1,6 @@
// Limit MR description for side-by-side diff view
.fixed-width-container {
- max-width: $limited-layout-width - ($gl-padding * 2);
- margin-left: auto;
- margin-right: auto;
+ @include fixed-width-container;
}
.issuable-warning-icon {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 45382d4ea43..2feb7464ecb 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;
@@ -201,7 +200,6 @@
.mr-widget-icon {
font-size: 22px;
- margin-right: $gl-btn-padding;
}
.ci-status-icon svg {
@@ -455,7 +453,7 @@
.mr-list {
.merge-request {
- padding: 10px 0 10px 15px;
+ padding: 10px 0 10px 15px;
position: relative;
display: -webkit-flex;
display: flex;
@@ -469,7 +467,6 @@
margin-bottom: 2px;
.ci-status-link {
-
svg {
height: 16px;
width: 16px;
@@ -699,7 +696,6 @@
.table-holder {
.ci-table {
-
th {
background-color: $white-light;
color: $gl-text-color-secondary;
@@ -776,7 +772,7 @@
&.affix {
left: 0;
- transition: right .15s;
+ transition: right 0.15s;
@include media-breakpoint-down(xs) {
right: 0;
@@ -885,7 +881,7 @@
}
> *:not(:last-child) {
- margin-right: .3em;
+ margin-right: 0.3em;
}
svg {
@@ -908,6 +904,10 @@
.btn svg {
fill: $theme-gray-700;
}
+
+ .dropdown-menu {
+ width: 400px;
+ }
}
// Hack alert: we've rewritten `btn` class in a way that
@@ -918,7 +918,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/settings.scss b/app/assets/stylesheets/pages/settings.scss
index dbf8692d69b..ccfa4e00a5b 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -42,6 +42,10 @@
margin-top: 0;
}
+ .settings-title {
+ cursor: pointer;
+ }
+
button {
position: absolute;
top: 20px;
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 59fdbf31fe9..9c01a2f8bda 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -68,6 +68,10 @@
}
}
+ .current-host.canary {
+ color: $perf-bar-canary-text;
+ }
+
strong {
color: $white-light;
}
diff --git a/app/controllers/admin/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/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
index 44864f9c7d0..25cc241e5b0 100644
--- a/app/controllers/admin/health_check_controller.rb
+++ b/app/controllers/admin/health_check_controller.rb
@@ -3,12 +3,5 @@
class Admin::HealthCheckController < Admin::ApplicationController
def show
@errors = HealthCheck::Utils.process_checks(['standard'])
- @failing_storage_statuses = Gitlab::Git::Storage::Health.for_failing_storages
- end
-
- def reset_storage_health
- Gitlab::Git::Storage::FailureInfo.reset_all!
- redirect_to admin_health_check_path,
- notice: _('Git storage health information has been reset')
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index ec45e2813c5..eeabcc0c9bb 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,7 +12,9 @@ class ApplicationController < ActionController::Base
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
- include InvalidUTF8ErrorHandler
+ # this can be removed after switching to rails 5
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/51908
+ include InvalidUTF8ErrorHandler unless Gitlab.rails5?
before_action :authenticate_sessionless_user!
before_action :authenticate_user!
@@ -66,7 +68,7 @@ class ApplicationController < ActionController::Base
head :forbidden, retry_after: Gitlab::Auth::UniqueIpsLimiter.config.unique_ips_limit_time_window
end
- rescue_from Gitlab::Git::Storage::Inaccessible, GRPC::Unavailable, Gitlab::Git::CommandError do |exception|
+ rescue_from GRPC::Unavailable, Gitlab::Git::CommandError do |exception|
log_exception(exception)
headers['Retry-After'] = exception.retry_after if exception.respond_to?(:retry_after)
diff --git a/app/controllers/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/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/health_controller.rb b/app/controllers/health_controller.rb
index ab4bc911e17..dc9a52f8da5 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class HealthController < ActionController::Base
- protect_from_forgery with: :exception, except: :storage_check, prepend: true
+ protect_from_forgery with: :exception, prepend: true
include RequiresWhitelistedMonitoringClient
CHECKS = [
@@ -25,15 +25,6 @@ class HealthController < ActionController::Base
render_check_results(results)
end
- def storage_check
- results = Gitlab::Git::Storage::Checker.check_all
-
- render json: {
- check_interval: Gitlab::CurrentSettings.current_application_settings.circuitbreaker_check_interval,
- results: results
- }
- end
-
private
def render_check_results(results)
diff --git a/app/controllers/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/koding_controller.rb b/app/controllers/koding_controller.rb
deleted file mode 100644
index 72aa9d4f17f..00000000000
--- a/app/controllers/koding_controller.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-class KodingController < ApplicationController
- before_action :check_integration!
- layout 'koding'
-
- def index
- path = File.join(Rails.root, 'doc/user/project/koding.md')
- @markdown = File.read(path)
- end
-
- private
-
- def check_integration!
- render_404 unless Gitlab::CurrentSettings.koding_enabled?
- end
-end
diff --git a/app/controllers/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/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index d0f59aa8162..312e256ea6c 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -10,7 +10,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
before_action :authorize_update_build!, only: [:keep]
before_action :extract_ref_name_and_path
before_action :set_request_format, only: [:file]
- before_action :validate_artifacts!
+ before_action :validate_artifacts!, except: [:download]
before_action :entry, only: [:file]
def download
@@ -102,7 +102,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
# rubocop: enable CodeReuse/ActiveRecord
def artifacts_file
- @artifacts_file ||= build.artifacts_file_for_type(params[:file_type] || :archive)
+ @artifacts_file ||= build&.artifacts_file_for_type(params[:file_type] || :archive)
end
def entry
diff --git a/app/controllers/projects/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/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
index 46449a4aae9..7d4d566499c 100644
--- a/app/controllers/projects/build_artifacts_controller.rb
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -6,10 +6,10 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
before_action :authorize_read_build!
before_action :extract_ref_name_and_path
- before_action :validate_artifacts!
+ before_action :validate_artifacts!, except: [:download]
def download
- redirect_to download_project_job_artifacts_path(project, job)
+ redirect_to download_project_job_artifacts_path(project, job, params: request.query_parameters)
end
def browse
diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb
index c356f8d2987..bcea96bce94 100644
--- a/app/controllers/projects/clusters/applications_controller.rb
+++ b/app/controllers/projects/clusters/applications_controller.rb
@@ -2,31 +2,20 @@
class Projects::Clusters::ApplicationsController < Projects::ApplicationController
before_action :cluster
- before_action :application_class, only: [:create]
before_action :authorize_read_cluster!
before_action :authorize_create_cluster!, only: [:create]
- # rubocop: disable CodeReuse/ActiveRecord
def create
- application = @application_class.find_or_initialize_by(cluster: @cluster)
-
- if application.has_attribute?(:hostname)
- application.hostname = params[:hostname]
- end
-
- if application.respond_to?(:oauth_application)
- application.oauth_application = create_oauth_application(application)
- end
-
- application.save!
-
- Clusters::Applications::ScheduleInstallationService.new(project, current_user).execute(application)
+ Clusters::Applications::CreateService
+ .new(@cluster, current_user, create_cluster_application_params)
+ .execute(request)
head :no_content
+ rescue Clusters::Applications::CreateService::InvalidApplicationError
+ render_404
rescue StandardError
head :bad_request
end
- # rubocop: enable CodeReuse/ActiveRecord
private
@@ -34,18 +23,7 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll
@cluster ||= project.clusters.find(params[:id]) || render_404
end
- def application_class
- @application_class ||= Clusters::Cluster::APPLICATIONS[params[:application]] || render_404
- end
-
- def create_oauth_application(application)
- oauth_application_params = {
- name: params[:application],
- redirect_uri: application.callback_url,
- scopes: 'api read_user openid',
- owner: current_user
- }
-
- Applications::CreateService.new(current_user, oauth_application_params).execute(request)
+ def create_cluster_application_params
+ params.permit(:application, :hostname)
end
end
diff --git a/app/controllers/projects/clusters_controller.rb b/app/controllers/projects/clusters_controller.rb
index bcdbf48bb35..62adc66fb09 100644
--- a/app/controllers/projects/clusters_controller.rb
+++ b/app/controllers/projects/clusters_controller.rb
@@ -40,7 +40,7 @@ class Projects::ClustersController < Projects::ApplicationController
def update
Clusters::UpdateService
- .new(project, current_user, update_params)
+ .new(current_user, update_params)
.execute(cluster)
if cluster.valid?
@@ -73,8 +73,8 @@ class Projects::ClustersController < Projects::ApplicationController
def create_gcp
@gcp_cluster = ::Clusters::CreateService
- .new(project, current_user, create_gcp_cluster_params)
- .execute(token_in_session)
+ .new(current_user, create_gcp_cluster_params)
+ .execute(project: project, access_token: token_in_session)
if @gcp_cluster.persisted?
redirect_to project_cluster_path(project, @gcp_cluster)
@@ -89,8 +89,8 @@ class Projects::ClustersController < Projects::ApplicationController
def create_user
@user_cluster = ::Clusters::CreateService
- .new(project, current_user, create_user_cluster_params)
- .execute(token_in_session)
+ .new(current_user, create_user_cluster_params)
+ .execute(project: project, access_token: token_in_session)
if @user_cluster.persisted?
redirect_to project_cluster_path(project, @user_cluster)
diff --git a/app/controllers/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/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/controllers/users_controller.rb b/app/controllers/users_controller.rb
index d16240af404..5b70c69d7f4 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -56,10 +56,12 @@ class UsersController < ApplicationController
def projects
load_projects
+ skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination])
+
respond_to do |format|
format.html { render 'show' }
format.json do
- pager_json("shared/projects/_list", @projects.count, projects: @projects)
+ pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination)
end
end
end
diff --git a/app/finders/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/branches_finder.rb b/app/finders/branches_finder.rb
index 970efa79dfb..45d5591e81b 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -7,8 +7,9 @@ class BranchesFinder
end
def execute
- branches = @repository.branches_sorted_by(sort)
- filter_by_name(branches)
+ branches = repository.branches_sorted_by(sort)
+ branches = by_search(branches)
+ branches
end
private
@@ -23,11 +24,39 @@ class BranchesFinder
@params[:sort].presence || 'name'
end
- def filter_by_name(branches)
- if search
- branches.select { |branch| branch.name.upcase.include?(search.upcase) }
+ def by_search(branches)
+ return branches unless search
+
+ case search
+ when ->(v) { v.starts_with?('^') }
+ filter_branches_with_prefix(branches, search.slice(1..-1).upcase)
+ when ->(v) { v.ends_with?('$') }
+ filter_branches_with_suffix(branches, search.chop.upcase)
else
- branches
+ matches = filter_branches_by_name(branches, search.upcase)
+ set_exact_match_as_first_result(matches, search)
end
end
+
+ def filter_branches_with_prefix(branches, prefix)
+ branches.select { |branch| branch.name.upcase.starts_with?(prefix) }
+ end
+
+ def filter_branches_with_suffix(branches, suffix)
+ branches.select { |branch| branch.name.upcase.ends_with?(suffix) }
+ end
+
+ def filter_branches_by_name(branches, term)
+ branches.select { |branch| branch.name.upcase.include?(term) }
+ end
+
+ def set_exact_match_as_first_result(matches, term)
+ exact_match_index = find_exact_match_index(matches, term)
+ matches.insert(0, matches.delete_at(exact_match_index)) if exact_match_index
+ matches
+ end
+
+ def find_exact_match_index(matches, term)
+ matches.index { |branch| branch.name.casecmp(term) == 0 }
+ end
end
diff --git a/app/finders/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/projects_finder.rb b/app/finders/projects_finder.rb
index c2404412006..6ececcd4152 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -42,17 +42,7 @@ class ProjectsFinder < UnionFinder
init_collection
end
- collection = by_ids(collection)
- collection = by_personal(collection)
- collection = by_starred(collection)
- collection = by_trending(collection)
- collection = by_visibilty_level(collection)
- collection = by_tags(collection)
- collection = by_search(collection)
- collection = by_archived(collection)
- collection = by_custom_attributes(collection)
- collection = by_deleted_status(collection)
-
+ collection = filter_projects(collection)
sort(collection)
end
@@ -66,6 +56,21 @@ class ProjectsFinder < UnionFinder
end
end
+ # EE would override this to add more filters
+ def filter_projects(collection)
+ collection = by_ids(collection)
+ collection = by_personal(collection)
+ collection = by_starred(collection)
+ collection = by_trending(collection)
+ collection = by_visibilty_level(collection)
+ collection = by_tags(collection)
+ collection = by_search(collection)
+ collection = by_archived(collection)
+ collection = by_custom_attributes(collection)
+ collection = by_deleted_status(collection)
+ collection
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def collection_with_user
if owned_projects?
diff --git a/app/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/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 15cbfeea609..0c9f69b6714 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -7,7 +7,6 @@ module ApplicationSettingsHelper
:gravatar_enabled?,
:password_authentication_enabled_for_web?,
:akismet_enabled?,
- :koding_enabled?,
to: :'Gitlab::CurrentSettings.current_application_settings'
def user_oauth_applications?
@@ -108,37 +107,6 @@ module ApplicationSettingsHelper
options_for_select(options, selected)
end
- def circuitbreaker_failure_count_help_text
- health_link = link_to(s_('AdminHealthPageLink|health page'), admin_health_check_path)
- api_link = link_to(s_('CircuitBreakerApiLink|circuitbreaker api'), help_page_path("api/repository_storage_health"))
- message = _("The number of failures of after which GitLab will completely "\
- "prevent access to the storage. The number of failures can be "\
- "reset in the admin interface: %{link_to_health_page} or using "\
- "the %{api_documentation_link}.")
- message = message % { link_to_health_page: health_link, api_documentation_link: api_link }
-
- message.html_safe
- end
-
- def circuitbreaker_access_retries_help_text
- _('The number of attempts GitLab will make to access a storage.')
- end
-
- def circuitbreaker_failure_reset_time_help_text
- _("The time in seconds GitLab will keep failure information. When no "\
- "failures occur during this time, information about the mount is reset.")
- end
-
- def circuitbreaker_storage_timeout_help_text
- _("The time in seconds GitLab will try to access storage. After this time a "\
- "timeout error will be raised.")
- end
-
- def circuitbreaker_check_interval_help_text
- _("The time in seconds between storage checks. When a previous check did "\
- "complete yet, GitLab will skip a check.")
- end
-
def visible_attributes
[
:admin_notification_email,
@@ -150,11 +118,6 @@ module ApplicationSettingsHelper
:authorized_keys_enabled,
:auto_devops_enabled,
:auto_devops_domain,
- :circuitbreaker_access_retries,
- :circuitbreaker_check_interval,
- :circuitbreaker_failure_count_threshold,
- :circuitbreaker_failure_reset_time,
- :circuitbreaker_storage_timeout,
:clientside_sentry_dsn,
:clientside_sentry_enabled,
:container_registry_token_expire_delay,
@@ -191,8 +154,6 @@ module ApplicationSettingsHelper
:housekeeping_incremental_repack_period,
:html_emails_enabled,
:import_sources,
- :koding_enabled,
- :koding_url,
:max_artifacts_size,
:max_attachment_size,
:max_pages_size,
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 883e5ddff57..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,27 +183,27 @@ 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,
- 'blob-language' => @blob && @blob.language.try(:ace_mode),
+ 'blob-filename' => @blob && @blob.path,
'project-id' => project.id
}
end
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/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 33c53021c11..463f4145bdd 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -21,29 +21,6 @@ module DashboardHelper
links.any? { |link| dashboard_nav_link?(link) }
end
- def controller_action_to_child_dashboards(controller = controller_name, action = action_name)
- case "#{controller}##{action}"
- when 'projects#index', 'root#index', 'projects#starred', 'projects#trending'
- %w(projects stars)
- when 'dashboard#activity'
- %w(starred_project_activity project_activity)
- when 'groups#index'
- %w(groups)
- when 'todos#index'
- %w(todos)
- when 'dashboard#issues'
- %w(issues)
- when 'dashboard#merge_requests'
- %w(merge_requests)
- else
- []
- end
- end
-
- def user_default_dashboard?(user = current_user)
- controller_action_to_child_dashboards.any? {|dashboard| dashboard == user.dashboard }
- end
-
private
def get_dashboard_nav_links
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index ff9842d4cd9..f4f46b0fe96 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -18,22 +18,20 @@ module PreferencesHelper
groups: _("Your Groups"),
todos: _("Your Todos"),
issues: _("Assigned Issues"),
- merge_requests: _("Assigned Merge Requests")
+ merge_requests: _("Assigned Merge Requests"),
+ operations: _("Operations Dashboard")
}.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text
def dashboard_choices
- defined = User.dashboards
+ dashboards = User.dashboards.keys
- if defined.size != DASHBOARD_CHOICES.size
- # Ensure that anyone adding new options updates this method too
- raise "`User` defines #{defined.size} dashboard choices," \
- " but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
- else
- defined.map do |key, _|
- # Use `fetch` so `KeyError` gets raised when a key is missing
- [DASHBOARD_CHOICES.fetch(key), key]
- end
+ validate_dashboard_choices!(dashboards)
+ dashboards -= excluded_dashboard_choices
+
+ dashboards.map do |key|
+ # Use `fetch` so `KeyError` gets raised when a key is missing
+ [DASHBOARD_CHOICES.fetch(key), key]
end
end
@@ -52,4 +50,20 @@ module PreferencesHelper
def user_color_scheme
Gitlab::ColorSchemes.for_user(current_user).css_class
end
+
+ private
+
+ # Ensure that anyone adding new options updates `DASHBOARD_CHOICES` too
+ def validate_dashboard_choices!(user_dashboards)
+ if user_dashboards.size != DASHBOARD_CHOICES.size
+ raise "`User` defines #{user_dashboards.size} dashboard choices," \
+ " but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
+ end
+ end
+
+ # List of dashboard choice to be excluded from CE.
+ # EE would override this.
+ def excluded_dashboard_choices
+ ['operations']
+ end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 0016f89db5c..d9713f9c9b0 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -388,22 +388,6 @@ module ProjectsHelper
end
end
- def koding_project_url(project = nil, branch = nil, sha = nil)
- if project
- import_path = "/Home/Stacks/import"
-
- repo = project.full_path
- branch ||= project.default_branch
- sha ||= project.commit.short_id
-
- path = "#{import_path}?repo=#{repo}&branch=#{branch}&sha=#{sha}"
-
- return URI.join(Gitlab::CurrentSettings.koding_url, path).to_s
- end
-
- Gitlab::CurrentSettings.koding_url
- end
-
def project_wiki_path_with_version(proj, page, version, is_newest)
url_params = is_newest ? {} : { version_id: version }
project_wiki_path(proj, page, url_params)
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index 53bd43d4861..8ed2a2ec9f4 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -48,15 +48,21 @@ module SortingHelper
def groups_sort_options_hash
{
- sort_value_name => sort_title_name,
- sort_value_name_desc => sort_title_name_desc,
+ sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
sort_value_recently_created => sort_title_recently_created,
- sort_value_oldest_created => sort_title_oldest_created,
+ sort_value_oldest_created => sort_title_oldest_created,
sort_value_recently_updated => sort_title_recently_updated,
- sort_value_oldest_updated => sort_title_oldest_updated
+ sort_value_oldest_updated => sort_title_oldest_updated
}
end
+ def subgroups_sort_options_hash
+ groups_sort_options_hash.merge(
+ sort_value_most_stars => sort_title_most_stars
+ )
+ end
+
def admin_groups_sort_options_hash
groups_sort_options_hash.merge(
sort_value_largest_group => sort_title_largest_group
diff --git a/app/helpers/storage_health_helper.rb b/app/helpers/storage_health_helper.rb
deleted file mode 100644
index 182e8e6641b..00000000000
--- a/app/helpers/storage_health_helper.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-module StorageHealthHelper
- def failing_storage_health_message(storage_health)
- storage_name = content_tag(:strong, h(storage_health.storage_name))
- host_names = h(storage_health.failing_on_hosts.to_sentence)
- translation_params = { storage_name: storage_name,
- host_names: host_names,
- failed_attempts: storage_health.total_failures }
-
- translation = n_('%{storage_name}: failed storage access attempt on host:',
- '%{storage_name}: %{failed_attempts} failed storage access attempts:',
- storage_health.total_failures) % translation_params
-
- translation.html_safe
- end
-
- def message_for_circuit_breaker(circuit_breaker)
- maximum_failures = circuit_breaker.failure_count_threshold
- current_failures = circuit_breaker.failure_count
-
- translation_params = { number_of_failures: current_failures,
- maximum_failures: maximum_failures }
-
- if circuit_breaker.circuit_broken?
- s_("%{number_of_failures} of %{maximum_failures} failures. GitLab will not "\
- "retry automatically. Reset storage information when the problem is "\
- "resolved.") % translation_params
- else
- _("%{number_of_failures} of %{maximum_failures} failures. GitLab will "\
- "allow access on the next attempt.") % translation_params
- end
- end
-end
diff --git a/app/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/mailers/notify.rb b/app/mailers/notify.rb
index f7347ee61b4..662f3e00047 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -118,6 +118,7 @@ class Notify < BaseMailer
add_unsubscription_headers_and_links
headers["X-GitLab-#{model.class.name}-ID"] = model.id
+ headers["X-GitLab-#{model.class.name}-IID"] = model.iid if model.respond_to?(:iid)
headers['X-GitLab-Reply-Key'] = reply_key
@reason = headers['X-GitLab-NotificationReason']
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 65a2f760f93..b66ec0ffab6 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -4,6 +4,7 @@ class ApplicationSetting < ActiveRecord::Base
include CacheableAttributes
include CacheMarkdownField
include TokenAuthenticatable
+ include IgnorableColumn
add_authentication_token_field :runners_registration_token
add_authentication_token_field :health_check_access_token
@@ -27,6 +28,14 @@ class ApplicationSetting < ActiveRecord::Base
serialize :domain_blacklist, Array # rubocop:disable Cop/ActiveRecordSerialize
serialize :repository_storages # rubocop:disable Cop/ActiveRecordSerialize
+ ignore_column :circuitbreaker_failure_count_threshold
+ ignore_column :circuitbreaker_failure_reset_time
+ ignore_column :circuitbreaker_storage_timeout
+ ignore_column :circuitbreaker_access_retries
+ ignore_column :circuitbreaker_check_interval
+ ignore_column :koding_url
+ ignore_column :koding_enabled
+
cache_markdown_field :sign_in_text
cache_markdown_field :help_page_text
cache_markdown_field :shared_runners_text, pipeline: :plain_markdown
@@ -93,10 +102,6 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :unique_ips_limit_enabled
- validates :koding_url,
- presence: true,
- if: :koding_enabled
-
validates :plantuml_url,
presence: true,
if: :plantuml_enabled
@@ -150,17 +155,6 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { greater_than_or_equal_to: 0 }
- validates :circuitbreaker_failure_count_threshold,
- :circuitbreaker_failure_reset_time,
- :circuitbreaker_storage_timeout,
- :circuitbreaker_check_interval,
- presence: true,
- numericality: { only_integer: true, greater_than_or_equal_to: 0 }
-
- validates :circuitbreaker_access_retries,
- presence: true,
- numericality: { only_integer: true, greater_than_or_equal_to: 1 }
-
validates :gitaly_timeout_default,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
@@ -256,8 +250,6 @@ class ApplicationSetting < ActiveRecord::Base
housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10,
import_sources: Settings.gitlab['import_sources'],
- koding_enabled: false,
- koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
mirror_available: true,
diff --git a/app/models/blob.rb b/app/models/blob.rb
index acc64ffca67..31a839274b5 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -162,7 +162,7 @@ class Blob < SimpleDelegator
if stored_externally?
if rich_viewer
rich_viewer.binary?
- elsif Linguist::Language.find_by_extension(name).any?
+ elsif known_extension?
false
elsif _mime_type
_mime_type.binary?
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index cb73fc74bb6..2b28b702b05 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -27,11 +27,15 @@ 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
}.freeze
belongs_to :project
@@ -100,7 +104,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/clusters/applications/jupyter.rb b/app/models/clusters/applications/jupyter.rb
index 7be6a14f585..e43a0fd1786 100644
--- a/app/models/clusters/applications/jupyter.rb
+++ b/app/models/clusters/applications/jupyter.rb
@@ -19,7 +19,7 @@ module Clusters
def set_initial_status
return unless not_installable?
- if cluster&.application_ingress_installed? && cluster.application_ingress.external_ip
+ if cluster&.application_ingress_available? && cluster.application_ingress.external_ip
self.status = 'installable'
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index a4a2e2b79a6..43bf852c7ec 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.34'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index d7011ef447a..20d53b8e620 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -43,8 +43,9 @@ module Clusters
delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true
delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true
- delegate :installed?, to: :application_helm, prefix: true, allow_nil: true
- delegate :installed?, to: :application_ingress, prefix: true, allow_nil: true
+ delegate :available?, to: :application_helm, prefix: true, allow_nil: true
+ delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
+ delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
enum platform_type: {
kubernetes: 1
diff --git a/app/models/clusters/concerns/application_core.rb b/app/models/clusters/concerns/application_core.rb
index e3deedfb036..683b45331f6 100644
--- a/app/models/clusters/concerns/application_core.rb
+++ b/app/models/clusters/concerns/application_core.rb
@@ -15,7 +15,7 @@ module Clusters
def set_initial_status
return unless not_installable?
- self.status = 'installable' if cluster&.application_helm_installed?
+ self.status = 'installable' if cluster&.application_helm_available?
end
def self.application_name
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index a9df59fc059..93bdf9c223d 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -66,6 +66,10 @@ module Clusters
end
end
end
+
+ def available?
+ installed? || updated?
+ end
end
end
end
diff --git a/app/models/concerns/blob_like.rb b/app/models/concerns/blob_like.rb
index e96fefe81c4..f20f01486a5 100644
--- a/app/models/concerns/blob_like.rb
+++ b/app/models/concerns/blob_like.rb
@@ -2,7 +2,7 @@
module BlobLike
extend ActiveSupport::Concern
- include Linguist::BlobHelper
+ include Gitlab::BlobHelper
def id
raise NotImplementedError
diff --git a/app/models/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/deployment.rb b/app/models/deployment.rb
index ed61135cb47..f1bce1a1daf 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -19,6 +19,17 @@ class Deployment < ActiveRecord::Base
after_create :create_ref
after_create :invalidate_cache
+ scope :for_environment, -> (environment) { where(environment_id: environment) }
+
+ def self.last_for_environment(environment)
+ ids = self
+ .for_environment(environment)
+ .select('MAX(id) AS id')
+ .group(:environment_id)
+ .map(&:id)
+ find(ids)
+ end
+
def commit
project.commit(sha)
end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 309bd4f37c9..0816c395185 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -48,6 +48,8 @@ class Environment < ActiveRecord::Base
order(Gitlab::Database.nulls_first_order("(#{max_deployment_id_sql})", 'ASC'))
end
scope :in_review_folder, -> { where(environment_type: "review") }
+ scope :for_name, -> (name) { where(name: name) }
+ scope :for_project, -> (project) { where(project_id: project) }
state_machine :state, initial: :available do
event :start do
diff --git a/app/models/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/project.rb b/app/models/project.rb
index 05e14c578b5..4798fae82b7 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -49,8 +49,11 @@ class Project < ActiveRecord::Base
attachments: 2
}.freeze
- # Valids ports to import from
- VALID_IMPORT_PORTS = [22, 80, 443].freeze
+ VALID_IMPORT_PORTS = [80, 443].freeze
+ VALID_IMPORT_PROTOCOLS = %w(http https git).freeze
+
+ VALID_MIRROR_PORTS = [22, 80, 443].freeze
+ VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
cache_markdown_field :description, pipeline: :description
@@ -164,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
@@ -305,10 +303,10 @@ class Project < ActiveRecord::Base
validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id }
- validates :import_url, url: { protocols: %w(http https ssh git),
+ validates :import_url, url: { protocols: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
+ ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
allow_localhost: false,
- enforce_user: true,
- ports: VALID_IMPORT_PORTS }, if: [:external_import?, :import_url_changed?]
+ enforce_user: true }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
@@ -690,6 +688,8 @@ class Project < ActiveRecord::Base
else
super
end
+ rescue
+ super
end
def valid_import_url?
@@ -1247,12 +1247,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
@@ -1543,9 +1538,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
@@ -1789,7 +1782,7 @@ class Project < ActiveRecord::Base
return unless export_file_exists?
import_export_upload.remove_export_file!
- import_export_upload.save
+ import_export_upload.save unless import_export_upload.destroyed?
end
def export_file_exists?
diff --git a/app/models/project_services/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/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 509e5b6089b..211e5c3fcbf 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -26,7 +26,7 @@ class PrometheusService < MonitoringService
end
def editable?
- manual_configuration? || !prometheus_installed?
+ manual_configuration? || !prometheus_available?
end
def title
@@ -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
}
]
@@ -75,17 +74,17 @@ class PrometheusService < MonitoringService
RestClient::Resource.new(api_url) if api_url && manual_configuration? && active?
end
- def prometheus_installed?
+ def prometheus_available?
return false if template?
return false unless project
- project.clusters.enabled.any? { |cluster| cluster.application_prometheus&.installed? }
+ project.clusters.enabled.any? { |cluster| cluster.application_prometheus_available? }
end
private
def synchronize_service_state
- self.active = prometheus_installed? || manual_configuration?
+ self.active = prometheus_available? || manual_configuration?
true
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a3a3ce179fc..37a1dd64052 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -36,7 +36,7 @@ class Repository
# For example, for entry `:commit_count` there's a method called `commit_count` which
# stores its data in the `commit_count` cache key.
CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
- changelog license_blob license_key gitignore koding_yml
+ changelog license_blob license_key gitignore
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? root_ref has_visible_content?
issue_template_names merge_request_template_names xcode_project?).freeze
@@ -53,7 +53,6 @@ class Repository
license: %i(license_blob license_key license),
contributing: :contribution_guide,
gitignore: :gitignore,
- koding: :koding_yml,
gitlab_ci: :gitlab_ci_yml,
avatar: :avatar,
issue_template: :issue_template_names,
@@ -488,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
@@ -570,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
@@ -619,11 +629,6 @@ class Repository
end
cache_method :gitignore
- def koding_yml
- file_on_head(:koding)
- end
- cache_method :koding_yml
-
def gitlab_ci_yml
file_on_head(:gitlab_ci)
end
@@ -881,10 +886,12 @@ class Repository
delegate :merged_branch_names, to: :raw_repository
- def merge_base(first_commit_id, second_commit_id)
- first_commit_id = commit(first_commit_id).try(:id) || first_commit_id
- second_commit_id = commit(second_commit_id).try(:id) || second_commit_id
- raw_repository.merge_base(first_commit_id, second_commit_id)
+ def merge_base(*commits_or_ids)
+ commit_ids = commits_or_ids.map do |commit_or_id|
+ commit_or_id.is_a?(::Commit) ? commit_or_id.id : commit_or_id
+ end
+
+ raw_repository.merge_base(*commit_ids)
end
def ancestor?(ancestor_id, descendant_id)
diff --git a/app/models/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 8a7acfb73b1..34efb22b359 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -217,7 +217,7 @@ class User < ActiveRecord::Base
# User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array.
- enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos, :issues, :merge_requests]
+ enum dashboard: [:projects, :stars, :project_activity, :starred_project_activity, :groups, :todos, :issues, :merge_requests, :operations]
# User's Project preference
# Note: When adding an option, it MUST go on the end of the array.
@@ -264,7 +264,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.
diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb
index 2c0e8659fc1..1cd05cf3aac 100644
--- a/app/models/user_callout.rb
+++ b/app/models/user_callout.rb
@@ -6,8 +6,7 @@ class UserCallout < ActiveRecord::Base
enum feature_name: {
gke_cluster_integration: 1,
gcp_signup_offer: 2,
- cluster_security_warning: 3,
- gold_trial: 4
+ cluster_security_warning: 3
}
validates :user, presence: true
diff --git a/app/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/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index d2434d96fd7..79cd3606aec 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -36,8 +36,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
contribution_guide_anchor_data,
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
kubernetes_cluster_anchor_data,
- gitlab_ci_anchor_data,
- koding_anchor_data
+ gitlab_ci_anchor_data
].compact.reject { |item| item.enabled }
end
@@ -125,43 +124,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
add_special_file_path(file_name: 'README.md')
end
- def add_koding_stack_path
- project_new_blob_path(
- project,
- default_branch || 'master',
- file_name: '.koding.yml',
- commit_message: "Add Koding stack script",
- content: <<-CONTENT.strip_heredoc
- provider:
- aws:
- access_key: '${var.aws_access_key}'
- secret_key: '${var.aws_secret_key}'
- resource:
- aws_instance:
- #{project.path}-vm:
- instance_type: t2.nano
- user_data: |-
-
- # Created by GitLab UI for :>
-
- echo _KD_NOTIFY_@Installing Base packages...@
-
- apt-get update -y
- apt-get install git -y
-
- echo _KD_NOTIFY_@Cloning #{project.name}...@
-
- export KODING_USER=${var.koding_user_username}
- export REPO_URL=#{root_url}${var.koding_queryString_repo}.git
- export BRANCH=${var.koding_queryString_branch}
-
- sudo -i -u $KODING_USER git clone $REPO_URL -b $BRANCH
-
- echo _KD_NOTIFY_@#{project.name} cloned.@
- CONTENT
- )
- end
-
def license_short_name
license = repository.license
license&.nickname || license&.name || 'LICENSE'
@@ -310,14 +272,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
end
end
- def koding_anchor_data
- if current_user && can_current_user_push_code? && koding_enabled? && repository.koding_yml.blank?
- AnchorData.new(false,
- _('Set up Koding'),
- add_koding_stack_path)
- end
- end
-
def tags_to_show
project.tag_list.take(MAX_TAGS_TO_SHOW) # rubocop: disable CodeReuse/ActiveRecord
end
@@ -363,8 +317,4 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
branch_name: branch_name
)
end
-
- def koding_enabled?
- Gitlab::CurrentSettings.koding_enabled?
- end
end
diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb
index 3d508a9a407..066a5b1885c 100644
--- a/app/serializers/build_details_entity.rb
+++ b/app/serializers/build_details_entity.rb
@@ -4,11 +4,12 @@ class BuildDetailsEntity < JobEntity
expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags
expose :has_trace?, as: :has_trace
+ expose :stage
expose :user, using: UserEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
- expose :deployment_status, if: -> (*) { build.has_environment? } do
+ expose :deployment_status, if: -> (*) { build.starts_environment? } do
expose :deployment_status, as: :status
expose :persisted_environment, as: :environment, with: EnvironmentEntity
diff --git a/app/serializers/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/issue_entity.rb b/app/serializers/issue_entity.rb
index 16a477c92fa..c3f7d4651fb 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -42,6 +42,6 @@ class IssueEntity < IssuableEntity
end
expose :preview_note_path do |issue|
- preview_markdown_path(issue.project, quick_actions_target_type: 'Issue', quick_actions_target_id: issue.id)
+ preview_markdown_path(issue.project, quick_actions_target_type: 'Issue', quick_actions_target_id: issue.iid)
end
end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 380e8804f51..9ec24f799ef 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -222,7 +222,7 @@ class MergeRequestWidgetEntity < IssuableEntity
end
expose :preview_note_path do |merge_request|
- preview_markdown_path(merge_request.project, quick_actions_target_type: 'MergeRequest', quick_actions_target_id: merge_request.id)
+ preview_markdown_path(merge_request.project, quick_actions_target_type: 'MergeRequest', quick_actions_target_id: merge_request.iid)
end
expose :merge_commit_path do |merge_request|
diff --git a/app/services/applications/create_service.rb b/app/services/applications/create_service.rb
index 3d88c4f064e..b6c30da4d3a 100644
--- a/app/services/applications/create_service.rb
+++ b/app/services/applications/create_service.rb
@@ -9,6 +9,7 @@ module Applications
end
# rubocop: enable CodeReuse/ActiveRecord
+ # EE would override and use `request` arg
def execute(request)
Doorkeeper::Application.create(@params)
end
diff --git a/app/services/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/applications/create_service.rb b/app/services/clusters/applications/create_service.rb
new file mode 100644
index 00000000000..55f917798de
--- /dev/null
+++ b/app/services/clusters/applications/create_service.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class CreateService
+ InvalidApplicationError = Class.new(StandardError)
+
+ attr_reader :cluster, :current_user, :params
+
+ def initialize(cluster, user, params = {})
+ @cluster = cluster
+ @current_user = user
+ @params = params.dup
+ end
+
+ def execute(request)
+ create_application.tap do |application|
+ if application.has_attribute?(:hostname)
+ application.hostname = params[:hostname]
+ end
+
+ if application.respond_to?(:oauth_application)
+ application.oauth_application = create_oauth_application(application, request)
+ end
+
+ application.save!
+
+ Clusters::Applications::ScheduleInstallationService.new(application).execute
+ end
+ end
+
+ private
+
+ def create_application
+ builder.call(@cluster)
+ end
+
+ def builder
+ builders[application_name] || raise(InvalidApplicationError, "invalid application: #{application_name}")
+ end
+
+ def builders
+ {
+ "helm" => -> (cluster) { cluster.application_helm || cluster.build_application_helm },
+ "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress },
+ "prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus },
+ "runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner },
+ "jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter }
+ }
+ end
+
+ def application_name
+ params[:application]
+ end
+
+ def create_oauth_application(application, request)
+ oauth_application_params = {
+ name: params[:application],
+ redirect_uri: application.callback_url,
+ scopes: 'api read_user openid',
+ owner: current_user
+ }
+
+ ::Applications::CreateService.new(current_user, oauth_application_params).execute(request)
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/applications/schedule_installation_service.rb b/app/services/clusters/applications/schedule_installation_service.rb
index 4ead4f619c8..d75ba70c27e 100644
--- a/app/services/clusters/applications/schedule_installation_service.rb
+++ b/app/services/clusters/applications/schedule_installation_service.rb
@@ -2,8 +2,14 @@
module Clusters
module Applications
- class ScheduleInstallationService < ::BaseService
- def execute(application)
+ class ScheduleInstallationService
+ attr_reader :application
+
+ def initialize(application)
+ @application = application
+ end
+
+ def execute
application.make_scheduled!
ClusterInstallAppWorker.perform_async(application.name, application.id)
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index e3e0cfa462c..c6e955800af 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -1,36 +1,34 @@
# frozen_string_literal: true
module Clusters
- class CreateService < BaseService
- attr_reader :access_token
+ class CreateService
+ attr_reader :current_user, :params
- def execute(access_token = nil)
- @access_token = access_token
+ def initialize(user = nil, params = {})
+ @current_user, @params = user, params.dup
+ end
- raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster?
+ def execute(project:, access_token: nil)
+ raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster?(project)
- create_cluster.tap do |cluster|
+ cluster_params = params.merge(user: current_user, projects: [project])
+ cluster_params[:provider_gcp_attributes].try do |provider|
+ provider[:access_token] = access_token
+ end
+
+ create_cluster(cluster_params).tap do |cluster|
ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
end
end
private
- def create_cluster
+ def create_cluster(cluster_params)
Clusters::Cluster.create(cluster_params)
end
- def cluster_params
- return @cluster_params if defined?(@cluster_params)
-
- params[:provider_gcp_attributes].try do |provider|
- provider[:access_token] = access_token
- end
-
- @cluster_params = params.merge(user: current_user, projects: [project])
- end
-
- def can_create_cluster?
+ # EE would override this method
+ def can_create_cluster?(project)
project.clusters.empty?
end
end
diff --git a/app/services/clusters/update_service.rb b/app/services/clusters/update_service.rb
index 98fdeec4fb1..25d26e761b1 100644
--- a/app/services/clusters/update_service.rb
+++ b/app/services/clusters/update_service.rb
@@ -1,7 +1,13 @@
# frozen_string_literal: true
module Clusters
- class UpdateService < BaseService
+ class UpdateService
+ attr_reader :current_user, :params
+
+ def initialize(user = nil, params = {})
+ @current_user, @params = user, params.dup
+ end
+
def execute(cluster)
cluster.update(params)
end
diff --git a/app/services/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/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/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/system_note_service.rb b/app/services/system_note_service.rb
index 575678da1fa..729bc991294 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -211,7 +211,7 @@ module SystemNoteService
# "closed via bc17db76"
#
# Returns the created Note object
- def change_status(noteable, project, author, status, source)
+ def change_status(noteable, project, author, status, source = nil)
body = status.dup
body << " via #{source.gfm_reference(project)}" if source
diff --git a/app/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/application_settings/_koding.html.haml b/app/views/admin/application_settings/_koding.html.haml
deleted file mode 100644
index 8b635b08abd..00000000000
--- a/app/views/admin/application_settings/_koding.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-koding-settings'), html: { class: 'fieldset-form' } do |f|
- = form_errors(@application_setting)
-
- %fieldset
- .form-group
- .form-check
- = f.check_box :koding_enabled, class: 'form-check-input'
- = f.label :koding_enabled, class: 'form-check-label' do
- Enable Koding
- .form-text.text-muted
- Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again.
- .form-group
- = f.label :koding_url, 'Koding URL', class: 'label-bold'
- = f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090'
- .form-text.text-muted
- Koding has integration enabled out of the box for the
- %strong gitlab
- team, and you need to provide that team's URL here. Learn more in the
- = succeed "." do
- = link_to "Koding administration documentation", help_page_path("administration/integration/koding")
-
- = f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_repository_storage.html.haml b/app/views/admin/application_settings/_repository_storage.html.haml
index 908b30cc3ce..c6c29ed1f21 100644
--- a/app/views/admin/application_settings/_repository_storage.html.haml
+++ b/app/views/admin/application_settings/_repository_storage.html.haml
@@ -20,32 +20,5 @@
Manage repository storage paths. Learn more in the
= succeed "." do
= link_to "repository storages documentation", help_page_path("administration/repository_storage_paths")
- .sub-section
- %h4 Circuit breaker
- .form-group
- = f.label :circuitbreaker_check_interval, _('Check interval'), class: 'label-bold'
- = f.number_field :circuitbreaker_check_interval, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_check_interval_help_text
- .form-group
- = f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'label-bold'
- = f.number_field :circuitbreaker_access_retries, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_access_retries_help_text
- .form-group
- = f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'label-bold'
- = f.number_field :circuitbreaker_storage_timeout, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_storage_timeout_help_text
- .form-group
- = f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'label-bold'
- = f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_failure_count_help_text
- .form-group
- = f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'label-bold'
- = f.number_field :circuitbreaker_failure_reset_time, class: 'form-control'
- .form-text.text-muted
- = circuitbreaker_failure_reset_time_help_text
= f.submit 'Save changes', class: "btn btn-success qa-save-changes-button"
diff --git a/app/views/admin/application_settings/repository.html.haml b/app/views/admin/application_settings/repository.html.haml
index be13138a764..b50a0dd5a18 100644
--- a/app/views/admin/application_settings/repository.html.haml
+++ b/app/views/admin/application_settings/repository.html.haml
@@ -20,7 +20,7 @@
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
- = _('Configure storage path and circuit breaker settings.')
+ = _('Configure storage path settings.')
.settings-content
= render 'repository_storage'
diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml
index 279db189a24..65e4723afe6 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -68,18 +68,6 @@
.settings-content
= render 'terms'
-- if koding_enabled?
- %section.settings.as-koding.no-animate#js-koding-settings{ class: ('expanded' if expanded_by_default?) }
- .settings-header
- %h4
- = _('Koding')
- %button.btn.btn-default.js-settings-toggle{ type: 'button' }
- = expanded_by_default? ? _('Collapse') : _('Expand')
- %p
- = _('Online IDE integration settings.')
- .settings-content
- = render 'koding'
-
= render_if_exists 'admin/application_settings/external_authorization_service_form', expanded: expanded_by_default?
%section.settings.as-terminal.no-animate#js-terminal-settings{ class: ('expanded' if expanded_by_default?) }
diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml
index e69143abe45..df3eeba907c 100644
--- a/app/views/admin/applications/show.html.haml
+++ b/app/views/admin/applications/show.html.haml
@@ -22,7 +22,7 @@
.input-group
%input.label.label-monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
.input-group-append
- = clipboard_button(target: '#application_id', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
+ = clipboard_button(target: '#secret', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
%tr
%td
= _('Callback URL')
diff --git a/app/views/admin/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/_form.html.haml b/app/views/admin/groups/_form.html.haml
index 2a117c1414e..5e05568e384 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -10,11 +10,11 @@
.col-sm-10
= render 'shared/choose_group_avatar_button', f: f
- = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
+ = render 'shared/old_visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
.form-group.row
.offset-sm-2.col-sm-10
- = render 'shared/allow_request_access', form: f
+ = render 'shared/allow_request_access', form: f, bold_label: true
= render 'groups/group_admin_settings', f: f
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 0c683f86252..21c1260e982 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -119,7 +119,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/health_check/_failing_storages.html.haml b/app/views/admin/health_check/_failing_storages.html.haml
deleted file mode 100644
index 6830201538d..00000000000
--- a/app/views/admin/health_check/_failing_storages.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-- if failing_storages.any?
- = _('There are problems accessing Git storage: ')
- %ul
- - failing_storages.each do |storage_health|
- %li
- = failing_storage_health_message(storage_health)
- %ul
- - storage_health.failing_circuit_breakers.each do |circuit_breaker|
- %li
- #{circuit_breaker.hostname}: #{message_for_circuit_breaker(circuit_breaker)}
-
- = _("Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again.")
- .prepend-top-10
- = button_to _("Reset git storage health information"), reset_storage_health_admin_health_check_path,
- method: :post, class: 'btn btn-default'
diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
index d51ac854b04..0f5e97e288a 100644
--- a/app/views/admin/health_check/show.html.haml
+++ b/app/views/admin/health_check/show.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
- page_title _('Health Check')
-- no_errors = @errors.blank? && @failing_storage_statuses.blank?
+- no_errors = @errors.blank?
%div{ class: container_class }
%h3.page-title= page_title
@@ -39,4 +39,3 @@
#{ s_('HealthCheck|No Health Problems Detected') }
- else
= @errors
- = render partial: 'failing_storages', object: @failing_storage_statuses
diff --git a/app/views/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/activity.html.haml b/app/views/dashboard/activity.html.haml
index 3cee5841bbc..31d4b3da4f1 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -4,9 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 985928305a2..50f39f93283 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -3,9 +3,6 @@
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if params[:filter].blank? && @groups.empty?
= render 'shared/groups/empty_state'
- else
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 91f58ddcfcc..832ba877558 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,14 +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")
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
+.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 27f53a8d1c6..fba8d1cf667 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -2,13 +2,15 @@
- page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id)
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
+.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/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index f0d16936a51..deed774a4a5 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -4,9 +4,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 42638b8528d..8933d9e31ff 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -4,9 +4,6 @@
- page_title "Starred Projects"
- header_title "Projects", dashboard_projects_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
%div{ class: container_class }
= render "projects/last_push"
= render 'dashboard/projects_head'
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index bbfa4cc7413..d2593179f17 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -2,8 +2,8 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
+.page-title-holder
+ %h1.page-title= _('Todos')
- if current_user.todos.any?
.top-area
diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
index 269a3721e06..12271ee5adb 100644
--- a/app/views/devise/shared/_omniauth_box.html.haml
+++ b/app/views/devise/shared/_omniauth_box.html.haml
@@ -5,7 +5,7 @@
.d-flex.justify-content-between.flex-wrap
- providers.each do |provider|
- has_icon = provider_has_icon?(provider)
- = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'btn d-flex align-items-center omniauth-btn text-left oauth-login', id: "oauth-login-#{provider}" do
+ = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'btn d-flex align-items-center omniauth-btn text-left oauth-login qa-saml-login-button', id: "oauth-login-#{provider}" do
- if has_icon
= provider_image_tag(provider)
%span
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 90ed20404c5..004a3528d4b 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -5,26 +5,26 @@
= devise_error_messages!
.form-group
= f.label :name, 'Full name', class: 'label-bold'
- = f.text_field :name, class: "form-control top", required: true, title: "This field is required."
+ = f.text_field :name, class: "form-control top qa-new-user-name", required: true, title: "This field is required."
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
+ = f.text_field :username, class: "form-control middle qa-new-user-username", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: 'Please create a username with only alphanumeric characters.'
%p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability...
.form-group
= f.label :email, class: 'label-bold'
- = f.email_field :email, class: "form-control middle", required: true, title: "Please provide a valid email address."
+ = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: "Please provide a valid email address."
.form-group
= f.label :email_confirmation, class: 'label-bold'
- = f.email_field :email_confirmation, class: "form-control middle", required: true, title: "Please retype the email address."
+ = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: "Please retype the email address."
.form-group.append-bottom-20#password-strength
= f.label :password, class: 'label-bold'
- = f.password_field :password, class: "form-control bottom", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters."
+ = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters."
%p.gl-field-hint.text-secondary Minimum length is #{@minimum_password_length} characters
- if Gitlab::CurrentSettings.current_application_settings.enforce_terms?
.form-group
- = check_box_tag :terms_opt_in, '1', false, required: true
+ = check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms'
= label_tag :terms_opt_in do
- terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank"
- accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link }
@@ -33,4 +33,4 @@
- if Gitlab::Recaptcha.enabled?
= recaptcha_tags
.submit-container
- = f.submit "Register", class: "btn-register btn"
+ = f.submit "Register", class: "btn-register btn qa-new-user-register-button"
diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml
index 776bbc36ec2..cac00f9c854 100644
--- a/app/views/doorkeeper/applications/show.html.haml
+++ b/app/views/doorkeeper/applications/show.html.haml
@@ -25,7 +25,7 @@
.input-group
%input.label.label-monospace{ id: "secret", type: "text", autocomplete: 'off', value: @application.secret, readonly: true }
.input-group-append
- = clipboard_button(target: '#application_id', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
+ = clipboard_button(target: '#secret', title: _("Copy secret to clipboard"), class: "btn btn btn-default")
%tr
%td
= _('Callback URL')
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 1d8b9c5bc8f..a3eafc61d0a 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -2,9 +2,6 @@
- page_title _("Groups")
- header_title _("Groups"), dashboard_groups_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/groups_head'
- else
@@ -13,7 +10,7 @@
- if cookies[:explore_groups_landing_dismissed] != 'true'
.explore-groups.landing.content-block.js-explore-groups-landing.hide
- %button.dismiss-button{ type: 'button', 'aria-label' => _('Dismiss') }= icon('times')
+ %button.dismiss-button{ type: 'button', 'aria-label' => _('Dismiss') }= sprite_icon('close', size: 16)
.svg-container
= custom_icon('icon_explore_groups_splash')
.inner-content
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index 16be5791f83..452f390695c 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -2,9 +2,6 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index 16be5791f83..452f390695c 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -2,9 +2,6 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index 16be5791f83..452f390695c 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -2,9 +2,6 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= content_for :above_breadcrumbs_content do
- = render_if_exists "shared/gold_trial_callout"
-
- if current_user
= render 'dashboard/projects_head'
- else
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index fc17dd2d310..869c54d89ea 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -3,31 +3,31 @@
- expanded = Rails.env.test?
-%section.settings.gs-general.no-animate#js-general-settings{ class: ('expanded' if expanded) }
+%section.settings.gs-general.no-animate#js-general-settings{ class: ('expanded') }
.settings-header
- %h4
- = _('General')
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
+ = _('Naming, visibility')
%button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
+ = _('Collapse')
%p
- = _('Update your group name, description, avatar, and other general settings.')
+ = _('Update your group name, description, avatar, and visibility.')
.settings-content
= render 'groups/settings/general'
%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
- = _('Permissions')
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
+ = _('Permissions, LFS, 2FA')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
- = _('Enable or disable certain group features and choose access levels.')
+ = _('Advanced permissions, Large File Storage and Two-Factor authentication settings.')
.settings-content
= render 'groups/settings/permissions'
-%section.settings.no-animate{ class: ('expanded' if expanded) }
+%section.settings.no-animate#js-badge-settings{ class: ('expanded' if expanded) }
.settings-header
- %h4
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
= s_('GroupSettings|Badges')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
@@ -37,10 +37,12 @@
.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
- = _('Advanced')
+ %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
+ = _('Path, transfer, remove')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 683129fdf6e..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
@@ -27,7 +26,7 @@
.col-sm-10
= render 'shared/choose_group_avatar_button', f: f
- = render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group
+ = render 'shared/old_visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group, with_label: false
= render 'create_chat_team', f: f if Gitlab.config.mattermost.enabled
diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml
index 3814d45929d..5d211d0e186 100644
--- a/app/views/groups/settings/_advanced.html.haml
+++ b/app/views/groups/settings/_advanced.html.haml
@@ -23,16 +23,6 @@
= f.submit 'Change group path', class: 'btn btn-warning'
-.sub-section
- %h4.danger-title Remove group
- = form_tag(@group, method: :delete) do
- %p
- Removing group will cause all child projects and resources to be removed.
- %br
- %strong Removed group can not be restored!
-
- = button_to 'Remove group', '#', class: 'btn btn-remove js-confirm-danger', data: { 'confirm-danger-message' => remove_group_message(@group) }
-
- if supports_nested_groups?
.sub-section
%h4.warning-title Transfer group
@@ -47,3 +37,13 @@
%li You will need to update your local repositories to point to the new location.
%li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
= f.submit 'Transfer group', class: 'btn btn-warning'
+
+.sub-section
+ %h4.danger-title= _('Remove group')
+ = form_tag(@group, method: :delete) do
+ %p
+ = _('Removing group will cause all child projects and resources to be removed.')
+ %br
+ %strong= _('Removed group can not be restored!')
+
+ = button_to _('Remove group'), '#', class: 'btn btn-remove js-confirm-danger', data: { 'confirm-danger-message' => remove_group_message(@group) }
diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml
index 0e225fe33a5..0424ece037d 100644
--- a/app/views/groups/settings/_general.html.haml
+++ b/app/views/groups/settings/_general.html.haml
@@ -1,39 +1,33 @@
-= form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f|
+= form_for @group, html: { multipart: true, class: 'gl-show-field-errors js-general-settings-form' }, authenticity_token: true do |f|
%input{ type: 'hidden', name: 'update_section', value: 'js-general-settings' }
= form_errors(@group)
%fieldset
.row
- .form-group.col-md-9
- = f.label :name, class: 'label-bold' do
- Group name
+ .form-group.col-md-5
+ = f.label :name, _('Group name'), class: 'label-bold'
= f.text_field :name, class: 'form-control'
- .form-group.col-md-3
- = f.label :id, class: 'label-bold' do
- Group ID
- = f.text_field :id, class: 'form-control', readonly: true
+ .form-group.col-md-7
+ = f.label :id, _('Group ID'), class: 'label-bold'
+ = f.text_field :id, class: 'form-control w-auto', readonly: true
- .form-group
- = f.label :description, class: 'label-bold' do
- Group description
- %span.light (optional)
- = f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
+ .row.prepend-top-8
+ .form-group.col-md-9.append-bottom-0
+ = f.label :description, _('Group description (optional)'), class: 'label-bold'
+ = f.text_area :description, class: 'form-control', rows: 3, maxlength: 250
= render_if_exists 'shared/repository_size_limit_setting', form: f, type: :group
- .form-group.row
- .col-sm-12
- .avatar-container.s160
- = group_icon(@group, alt: '', class: 'avatar group-avatar s160')
- %p.light
- - if @group.avatar?
- You can change the group avatar here
- - else
- You can upload a group avatar here
- = render 'shared/choose_group_avatar_button', f: f
- - if @group.avatar?
- %hr
- = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-danger btn-inverted'
+ .form-group.prepend-top-default.append-bottom-20
+ .avatar-container.s90
+ = group_icon(@group, alt: '', class: 'avatar group-avatar s90')
+ = f.label :avatar, _('Group avatar'), class: 'label-bold d-block'
+ = render 'shared/choose_group_avatar_button', f: f
+ - if @group.avatar?
+ %hr
+ = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _('Avatar will be removed. Are you sure?')}, method: :delete, class: 'btn btn-danger btn-inverted'
- = f.submit 'Save group', class: 'btn btn-success'
+ = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
+
+ = f.submit _('Save changes'), class: 'btn btn-success mt-4 js-dirty-submit'
diff --git a/app/views/groups/settings/_lfs.html.haml b/app/views/groups/settings/_lfs.html.haml
new file mode 100644
index 00000000000..4674d561c12
--- /dev/null
+++ b/app/views/groups/settings/_lfs.html.haml
@@ -0,0 +1,15 @@
+- docs_link_url = help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: docs_link_url }
+
+%h5= _('Large File Storage')
+
+%p= s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
+
+.form-group.append-bottom-default
+ .form-check
+ = f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input'
+ = f.label :lfs_enabled, class: 'form-check-label' do
+ %span
+ = _('Allow projects within this group to use Git LFS')
+ %br/
+ %span.text-muted= _('This setting can be overridden in each project.')
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index 8dc88ec446c..6b0a6e7ed99 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -1,29 +1,24 @@
-= form_for @group, html: { multipart: true, class: 'gl-show-field-errors' }, authenticity_token: true do |f|
+= form_for @group, html: { multipart: true, class: 'gl-show-field-errors js-general-permissions-form' }, authenticity_token: true do |f|
%input{ type: 'hidden', name: 'update_section', value: 'js-permissions-settings' }
= form_errors(@group)
%fieldset
- = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
+ %h5= _('Permissions')
+ .form-group
+ = render 'shared/allow_request_access', form: f
- .form-group.row
- .offset-sm-2.col-sm-10
- = render 'shared/allow_request_access', form: f
-
- .form-group.row
- %label.col-form-label.col-sm-2.pt-0
- = s_('GroupSettings|Share with group lock')
- .col-sm-10
- .form-check
- = f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
- = f.label :share_with_group_lock, class: 'form-check-label' do
- %strong
- - group_link = link_to @group.name, group_path(@group)
- = s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
- %br
- %span.descr= share_with_group_lock_help_text(@group)
-
- = render 'groups/group_admin_settings', f: f
+ .form-group.append-bottom-default
+ .form-check
+ = f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
+ = f.label :share_with_group_lock, class: 'form-check-label' do
+ %span
+ - group_link = link_to @group.name, group_path(@group)
+ = s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
+ %br
+ %span.descr.text-muted= share_with_group_lock_help_text(@group)
+ = render 'groups/settings/lfs', f: f
+ = render 'groups/settings/two_factor_auth', f: f
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
- = f.submit 'Save group', class: 'btn btn-success'
+ = f.submit _('Save changes'), class: 'btn btn-success prepend-top-default js-dirty-submit'
diff --git a/app/views/groups/settings/_two_factor_auth.html.haml b/app/views/groups/settings/_two_factor_auth.html.haml
new file mode 100644
index 00000000000..5d3f1cbb279
--- /dev/null
+++ b/app/views/groups/settings/_two_factor_auth.html.haml
@@ -0,0 +1,16 @@
+- docs_link_url = help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group')
+- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: docs_link_url }
+
+%h5= _('Two-factor authentication')
+
+%p= s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
+
+.form-group
+ .form-check
+ = f.check_box :require_two_factor_authentication, class: 'form-check-input'
+ = f.label :require_two_factor_authentication, class: 'form-check-label' do
+ %span= _('Require all users in this group to setup Two-factor authentication')
+.form-group
+ = f.label :two_factor_grace_period, _('Time before enforced'), class: 'label-bold'
+ = f.text_field :two_factor_grace_period, class: 'form-control form-control-sm w-auto'
+ .form-text.text-muted= _('Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication')
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 6a293daaf95..cc294f6a931 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -53,7 +53,7 @@
= _("Archived projects")
.nav-controls
- = render "shared/groups/dropdown"
+ = render "shared/groups/dropdown", options_hash: subgroups_sort_options_hash
.tab-content
#subgroups_and_projects.tab-pane
diff --git a/app/views/koding/index.html.haml b/app/views/koding/index.html.haml
deleted file mode 100644
index bb7f9ba7ae4..00000000000
--- a/app/views/koding/index.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.row-content-block.second-block.center
- %p
- = icon('circle', class: 'cgreen')
- Integration is active for
- = link_to koding_project_url, target: '_blank', rel: 'noopener noreferrer' do
- #{Gitlab::CurrentSettings.koding_url}
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index a41d30da450..1420b0a4973 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -12,6 +12,7 @@
- unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs"
= render "layouts/flash"
+ .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/koding.html.haml b/app/views/layouts/koding.html.haml
deleted file mode 100644
index 45ccd38f687..00000000000
--- a/app/views/layouts/koding.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-- page_title _("Koding")
-- page_description _("Koding Dashboard")
-- header_title _("Koding"), koding_path
-
-= render template: "layouts/application"
diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml
index c35451827c8..f53bd2b5e4d 100644
--- a/app/views/layouts/nav/_breadcrumbs.html.haml
+++ b/app/views/layouts/nav/_breadcrumbs.html.haml
@@ -1,7 +1,6 @@
- container = @no_breadcrumb_container ? 'container-fluid' : container_class
- hide_top_links = @hide_top_links || false
-= yield :above_breadcrumbs_content
%nav.breadcrumbs{ role: "navigation", class: [container, @content_class] }
.breadcrumbs-container
- if defined?(@left_sidebar)
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index 5e467c862ab..8f8b6b454d9 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -66,6 +66,7 @@
- if Gitlab::Sherlock.enabled? || can?(current_user, :read_instance_statistics)
%li.line-separator.d-none.d-sm-block
+ = render_if_exists 'dashboard/operations/nav_link'
- if can?(current_user, :read_instance_statistics)
= nav_link(controller: [:conversational_development_index, :cohorts]) do
= link_to instance_statistics_root_path, title: _('Instance Statistics'), aria: { label: _('Instance Statistics') }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index 48025f9bd20..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')
@@ -309,7 +309,7 @@
%span
= _('General')
= nav_link(controller: :project_members) do
- = link_to project_project_members_path(@project), title: _('Members') do
+ = link_to project_project_members_path(@project), title: _('Members'), class: 'qa-link-members-settings' do
%span
= _('Members')
- if can_edit
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index ced6a2a0399..61ed951dea9 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -76,8 +76,6 @@
= render 'projects/buttons/download', project: @project, ref: @ref
.d-none.d-sm-inline-flex
= render 'projects/buttons/dropdown'
- .d-none.d-sm-inline-flex
- = render 'projects/buttons/koding'
.d-none.d-sm-inline-flex
= render 'shared/notifications/button', notification_setting: @notification_setting
diff --git a/app/views/projects/_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 5b092427496..bd46f5a4349 100644
--- a/app/views/projects/blob/_template_selectors.html.haml
+++ b/app/views/projects/blob/_template_selectors.html.haml
@@ -3,15 +3,15 @@
Template
.template-selector-dropdowns-wrap
.template-type-selector.js-template-type-selector-wrap.hidden
- = dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector', title: "Choose a template type" } )
+ = dropdown_tag("Choose type", options: { toggle_class: 'js-template-type-selector qa-template-type-dropdown', title: "Choose a template type" } )
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select, project: @project.name, fullname: @project.namespace.human_name } } )
+ = dropdown_tag("Apply a license template", options: { toggle_class: 'js-license-selector qa-license-dropdown', title: "Apply a license", filter: true, placeholder: "Filter", data: { data: licenses_for_select(@project), project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names } } )
+ = dropdown_tag("Apply a .gitignore template", options: { toggle_class: 'js-gitignore-selector qa-gitignore-dropdown', title: "Apply a template", filter: true, placeholder: "Filter", data: { data: gitignore_names(@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', 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', 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/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 8b9c52f0802..45515fb492f 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -8,7 +8,7 @@
- if show_menu
.project-action-button.dropdown.inline
- %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...'), 'data-display' => 'static' }
+ %a.btn.dropdown-toggle.has-tooltip.qa-create-new-dropdown{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...'), 'data-display' => 'static' }
= icon('plus')
= icon("caret-down")
%ul.dropdown-menu.dropdown-menu-right.project-home-dropdown
@@ -28,7 +28,7 @@
%li.dropdown-header= _('This repository')
- if can_push_code
- %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
+ %li.qa-new-file-option= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- unless @project.empty_repo?
%li= link_to _('New branch'), new_project_branch_path(@project)
%li= link_to _('New tag'), new_project_tag_path(@project)
diff --git a/app/views/projects/buttons/_koding.html.haml b/app/views/projects/buttons/_koding.html.haml
deleted file mode 100644
index e665ca61da8..00000000000
--- a/app/views/projects/buttons/_koding.html.haml
+++ /dev/null
@@ -1,3 +0,0 @@
-- if koding_enabled? && current_user && @repository.koding_yml && @project.can_current_user_push_code?
- = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank', rel: 'noopener noreferrer' do
- _('Run in IDE (Koding)')
diff --git a/app/views/projects/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/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml
index 141314b4e4e..73cfea0ef92 100644
--- a/app/views/projects/clusters/_banner.html.haml
+++ b/app/views/projects/clusters/_banner.html.haml
@@ -9,7 +9,7 @@
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
- if show_cluster_security_warning?
- .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning{ data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } }
- %button.close.js-close{ type: "button" } &times;
+ .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning
+ %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } &times;
= s_("ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application.")
= link_to s_("More information"), help_page_path('user/project/clusters/index.md', anchor: 'security-implications')
diff --git a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
index 85d1002243b..73b11d509d3 100644
--- a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
+++ b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml
@@ -1,6 +1,6 @@
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
-.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
- %button.close.js-close{ type: "button" } &times;
+.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert' }
+ %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } &times;
.gcp-signup-offer--content
.gcp-signup-offer--icon.append-right-8
= sprite_icon("information", size: 16)
diff --git a/app/views/projects/deploy_tokens/_form.html.haml b/app/views/projects/deploy_tokens/_form.html.haml
index 8b7535397bc..5412fcbc9d8 100644
--- a/app/views/projects/deploy_tokens/_form.html.haml
+++ b/app/views/projects/deploy_tokens/_form.html.haml
@@ -6,24 +6,24 @@
.form-group
= f.label :name, class: 'label-bold'
- = f.text_field :name, class: 'form-control', required: true
+ = f.text_field :name, class: 'form-control qa-deploy-token-name', required: true
.form-group
= f.label :expires_at, class: 'label-bold'
- = f.text_field :expires_at, class: 'datepicker form-control', value: f.object.expires_at
+ = f.text_field :expires_at, class: 'datepicker form-control qa-deploy-token-expires-at', value: f.object.expires_at
.form-group
= f.label :scopes, class: 'label-bold'
%fieldset.form-group.form-check
- = f.check_box :read_repository, class: 'form-check-input'
+ = f.check_box :read_repository, class: 'form-check-input qa-deploy-token-read-repository'
= label_tag ("deploy_token_read_repository"), 'read_repository', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read-only access to the repository')
- if container_registry_enabled?(project)
%fieldset.form-group.form-check
- = f.check_box :read_registry, class: 'form-check-input'
+ = f.check_box :read_registry, class: 'form-check-input qa-deploy-token-read-registry'
= label_tag ("deploy_token_read_registry"), 'read_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read-only access to the registry images')
.prepend-top-default
- = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success'
+ = f.submit s_('DeployTokens|Create deploy token'), class: 'btn btn-success qa-create-deploy-token'
diff --git a/app/views/projects/deploy_tokens/_index.html.haml b/app/views/projects/deploy_tokens/_index.html.haml
index 33faab0c510..4619522cfaf 100644
--- a/app/views/projects/deploy_tokens/_index.html.haml
+++ b/app/views/projects/deploy_tokens/_index.html.haml
@@ -1,6 +1,6 @@
- expanded = expand_deploy_tokens_section?(@new_deploy_token)
-%section.settings.no-animate#js-deploy-tokens{ class: ('expanded' if expanded) }
+%section.qa-deploy-tokens-settings.settings.no-animate#js-deploy-tokens{ class: ('expanded' if expanded) }
.settings-header
%h4= s_('DeployTokens|Deploy Tokens')
%button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
diff --git a/app/views/projects/deploy_tokens/_new_deploy_token.html.haml b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
index 5dd9ffba074..c805ee73acc 100644
--- a/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
+++ b/app/views/projects/deploy_tokens/_new_deploy_token.html.haml
@@ -1,18 +1,18 @@
-.created-deploy-token-container.info-well
+.qa-created-deploy-token-section.created-deploy-token-container.info-well
.well-segment
%h5.prepend-top-0
= s_('DeployTokens|Your New Deploy Token')
.form-group
.input-group
- = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus'
+ = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus qa-deploy-token-user'
.input-group-append
= clipboard_button(text: deploy_token.username, title: s_('DeployTokens|Copy username to clipboard'), placement: 'left')
%span.deploy-token-help-block.prepend-top-5.text-success= s_("DeployTokens|Use this username as a login.")
.form-group
.input-group
- = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus'
+ = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus qa-deploy-token'
.input-group-append
= clipboard_button(text: deploy_token.token, title: s_('DeployTokens|Copy deploy token to clipboard'), placement: 'left')
%span.deploy-token-help-block.prepend-top-5.text-danger= s_("DeployTokens|Use this token as a password. Make sure you save it - you won't be able to access it again.")
diff --git a/app/views/projects/jobs/_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 a5f814b722d..475bae887ec 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -1,50 +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') } }
+ #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/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 ccb83148ded..dbb563f51ea 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -30,5 +30,3 @@
%span.js-details-content.hide
= link_to @pipeline.sha, project_commit_path(@project, @pipeline.sha), class: "commit-sha commit-hash-full"
= clipboard_button(text: @pipeline.sha, title: "Copy commit SHA to clipboard")
-
- = render_if_exists "projects/pipelines/info_extension", pipeline: @pipeline
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 517fd249f6e..5e21442bb60 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -3,7 +3,7 @@
= form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f|
.form-group
= label_tag :user_ids, "Select members to invite", class: "label-bold"
- = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
+ = users_select_tag(:user_ids, multiple: true, class: "input-clamp qa-member-select-input", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
.form-group
= label_tag :access_level, "Choose a role permission", class: "label-bold"
.select-wrapper
@@ -17,5 +17,5 @@
= label_tag :expires_at, 'Access expiration date', class: 'label-bold'
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
- = f.submit "Add to project", class: "btn btn-success"
+ = f.submit "Add to project", class: "btn btn-success qa-add-member-button"
= link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 0c5a187f208..9682f8ac922 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -14,5 +14,5 @@
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search")
= render 'shared/members/sort_dropdown'
- %ul.content-list.members-list
+ %ul.content-list.members-list.qa-members-list
= render partial: 'shared/members/member', collection: members, as: :member
diff --git a/app/views/projects/services/prometheus/_configuration_banner.html.haml b/app/views/projects/services/prometheus/_configuration_banner.html.haml
index 898b55e4b39..dfcb1c5d240 100644
--- a/app/views/projects/services/prometheus/_configuration_banner.html.haml
+++ b/app/views/projects/services/prometheus/_configuration_banner.html.haml
@@ -7,7 +7,7 @@
- else
.container-fluid
.row
- - if service.prometheus_installed?
+ - if service.prometheus_available?
.col-sm-2
.svg-container
= image_tag 'illustrations/monitoring/getting_started.svg'
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index aba289c790f..283031b06da 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -8,8 +8,8 @@
= render partial: 'flash_messages', locals: { project: @project }
-- if @project.repository_exists? && !@project.empty_repo?
- - signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @project.default_branch)
+- if !@project.empty_repo? && can?(current_user, :download_code, @project)
+ - signatures_path = project_signatures_path(@project, @project.default_branch)
.js-signature-container{ data: { 'signatures-path': signatures_path } }
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
diff --git a/app/views/projects/tree/_tree_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/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 9d196075bf1..601e3f25852 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -82,7 +82,7 @@
- if can_collaborate
= succeed " " do
- = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default' do
+ = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do
= _('Web IDE')
= render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/app/views/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/_allow_request_access.html.haml b/app/views/shared/_allow_request_access.html.haml
index 92268e74b1e..a50f1877d08 100644
--- a/app/views/shared/_allow_request_access.html.haml
+++ b/app/views/shared/_allow_request_access.html.haml
@@ -1,6 +1,8 @@
+- label_class = local_assigns.fetch(:bold_label, false) ? 'font-weight-bold' : ''
+
.form-check
= form.check_box :request_access_enabled, class: 'form-check-input'
= form.label :request_access_enabled, class: 'form-check-label' do
- %strong Allow users to request access
+ %span{ class: label_class }= _('Allow users to request access')
%br
- %span.descr Allow users to request access if visibility is public or internal.
+ %span.text-muted= _('Allow users to request access if visibility is public or internal.')
diff --git a/app/views/shared/_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/_old_visibility_level.html.haml b/app/views/shared/_old_visibility_level.html.haml
new file mode 100644
index 00000000000..fd576e4fbea
--- /dev/null
+++ b/app/views/shared/_old_visibility_level.html.haml
@@ -0,0 +1,6 @@
+.form-group.row
+ .col-sm-2.col-form-label
+ = _('Visibility level')
+ = link_to icon('question-circle'), help_page_path("public_access/public_access")
+ .col-sm-10
+ = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_visibility_level, form_model: form_model, with_label: with_label
diff --git a/app/views/shared/_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/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml
index ba37b37a3b1..2f42a877beb 100644
--- a/app/views/shared/_visibility_level.html.haml
+++ b/app/views/shared/_visibility_level.html.haml
@@ -1,17 +1,19 @@
- with_label = local_assigns.fetch(:with_label, true)
-.form-group.row.visibility-level-setting
+.form-group.visibility-level-setting
- if with_label
- = f.label :visibility_level, class: 'col-form-label col-sm-2 pt-0' do
- Visibility Level
- = link_to icon('question-circle'), help_page_path("public_access/public_access")
- %div{ :class => (with_label ? "col-sm-10" : "col-sm-12") }
- - if can_change_visibility_level
- = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
- - else
- %div
- %span.info
- = visibility_level_icon(visibility_level)
- %strong
- = visibility_level_label(visibility_level)
- .light= visibility_level_description(visibility_level, form_model)
+ = f.label :visibility_level, _('Visibility level'), class: 'label-bold append-bottom-0'
+ %p
+ = _('Who can see this group?')
+ - visibility_docs_path = help_page_path('public_access/public_access')
+ - docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: visibility_docs_path }
+ = s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
+ - if can_change_visibility_level
+ = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
+ - else
+ %div
+ %span.info
+ = visibility_level_icon(visibility_level)
+ %strong
+ = visibility_level_label(visibility_level)
+ .light= visibility_level_description(visibility_level, form_model)
diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml
index 5351c9ce6a4..df3308abe0d 100644
--- a/app/views/shared/empty_states/_wikis.html.haml
+++ b/app/views/shared/empty_states/_wikis.html.haml
@@ -5,7 +5,7 @@
- create_link = link_to s_('WikiEmpty|Create your first page'), create_path, class: 'btn btn-success', title: s_('WikiEmpty|Create your first page')
= render layout: layout_path, locals: { image_path: 'illustrations/wiki_login_empty.svg' } do
- %h4
+ %h4.text-left
= s_('WikiEmpty|The wiki lets you write documentation for your project')
%p.text-left
= s_("WikiEmpty|A wiki is where you can store all the details about your project. This can include why you've created it, its principles, how to use it, and so on.")
diff --git a/app/views/shared/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/_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/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/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index e1da05d8f08..06eb3d03e31 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -8,6 +8,7 @@
- user = local_assigns[:user]
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
- remote = false unless local_assigns[:remote] == true
+- skip_pagination = false unless local_assigns[:skip_pagination] == true
.js-projects-list-holder
- if any_projects?(projects)
@@ -25,6 +26,6 @@
= icon('lock fw', base: 'circle', class: 'fa-lg private-fork-icon')
%strong= pluralize(@private_forks_count, 'private fork')
%span &nbsp;you have no access to.
- = paginate_collection(projects, remote: remote)
+ = paginate_collection(projects, remote: remote) unless skip_pagination
- else
.nothing-here-block No projects found
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 1b66d3acd40..cf9c3055499 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -14,7 +14,7 @@
= render 'shared/form_elements/description', model: @snippet, project: @project, form: f
- = render 'shared/visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet
+ = render 'shared/old_visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet, with_label: false
.file-editor
.form-group.row
diff --git a/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