summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorAlex Braha Stoll <alexbrahastoll@gmail.com>2017-01-29 23:58:56 -0200
committerAlex Braha Stoll <alexbrahastoll@gmail.com>2017-01-29 23:58:56 -0200
commit4c57fa4282afc1679e2891b215174c92bf883c6a (patch)
tree2500cce3b212a86384f393cb26c6e83e18155490 /app
parent48417893d7456dc0d46b0a514a2326cc8ce6076f (diff)
parentb525aff665f139cd12ac5a6df78d722427e759cc (diff)
downloadgitlab-ce-4c57fa4282afc1679e2891b215174c92bf883c6a.tar.gz
Merge branch 'master' into 23535-folders-in-wiki-repository
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/admin.js4
-rw-r--r--app/assets/javascripts/api.js10
-rw-r--r--app/assets/javascripts/application.js10
-rw-r--r--app/assets/javascripts/aside.js4
-rw-r--r--app/assets/javascripts/autosave.js4
-rw-r--r--app/assets/javascripts/awards_handler.js14
-rw-r--r--app/assets/javascripts/behaviors/autosize.js3
-rw-r--r--app/assets/javascripts/behaviors/details_behavior.js3
-rw-r--r--app/assets/javascripts/behaviors/quick_submit.js3
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js3
-rw-r--r--app/assets/javascripts/blob/blob_ci_yaml.js.es64
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js4
-rw-r--r--app/assets/javascripts/blob/blob_gitignore_selector.js4
-rw-r--r--app/assets/javascripts/blob/blob_gitignore_selectors.js4
-rw-r--r--app/assets/javascripts/blob/blob_license_selector.js4
-rw-r--r--app/assets/javascripts/blob/blob_license_selectors.js.es63
-rw-r--r--app/assets/javascripts/blob/template_selector.js.es6164
-rw-r--r--app/assets/javascripts/blob_edit/blob_edit_bundle.js3
-rw-r--r--app/assets/javascripts/blob_edit/edit_blob.js6
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js.es66
-rw-r--r--app/assets/javascripts/boards/components/board.js.es632
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.js.es64
-rw-r--r--app/assets/javascripts/boards/components/board_list.js.es64
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js.es624
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js.es67
-rw-r--r--app/assets/javascripts/boards/models/issue.js.es610
-rw-r--r--app/assets/javascripts/boards/models/list.js.es616
-rw-r--r--app/assets/javascripts/boards/services/board_service.js.es64
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js.es620
-rw-r--r--app/assets/javascripts/boards/test_utils/simulate_drag.js231
-rw-r--r--app/assets/javascripts/boards/vue_resource_interceptor.js.es64
-rw-r--r--app/assets/javascripts/breakpoints.js4
-rw-r--r--app/assets/javascripts/broadcast_message.js3
-rw-r--r--app/assets/javascripts/build.js27
-rw-r--r--app/assets/javascripts/build_artifacts.js4
-rw-r--r--app/assets/javascripts/build_variables.js.es66
-rw-r--r--app/assets/javascripts/ci_lint_editor.js.es618
-rw-r--r--app/assets/javascripts/commit.js4
-rw-r--r--app/assets/javascripts/commit/file.js4
-rw-r--r--app/assets/javascripts/commit/image_file.js4
-rw-r--r--app/assets/javascripts/commits.js12
-rw-r--r--app/assets/javascripts/compare.js4
-rw-r--r--app/assets/javascripts/compare_autocomplete.js.es612
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js4
-rw-r--r--app/assets/javascripts/copy_as_gfm.js.es6355
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js3
-rw-r--r--app/assets/javascripts/diff.js.es625
-rw-r--r--app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es64
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es626
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js.es62
-rw-r--r--app/assets/javascripts/diff_notes/mixins/discussion.js.es66
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js.es68
-rw-r--r--app/assets/javascripts/diff_notes/stores/comments.js.es64
-rw-r--r--app/assets/javascripts/dispatcher.js.es625
-rw-r--r--app/assets/javascripts/droplab/droplab.js741
-rw-r--r--app/assets/javascripts/droplab/droplab_ajax.js96
-rw-r--r--app/assets/javascripts/droplab/droplab_ajax_filter.js161
-rw-r--r--app/assets/javascripts/droplab/droplab_filter.js74
-rw-r--r--app/assets/javascripts/dropzone_input.js6
-rw-r--r--app/assets/javascripts/due_date_select.js.es613
-rw-r--r--app/assets/javascripts/environments/components/environment.js.es662
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.js.es62
-rw-r--r--app/assets/javascripts/environments/components/environment_external_url.js.es62
-rw-r--r--app/assets/javascripts/environments/components/environment_item.js.es614
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.js.es62
-rw-r--r--app/assets/javascripts/environments/components/environment_stop.js.es62
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.js.es62
-rw-r--r--app/assets/javascripts/environments/environments_bundle.js.es611
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js.es663
-rw-r--r--app/assets/javascripts/extensions/array.js.es66
-rw-r--r--app/assets/javascripts/extensions/custom_event.js.es612
-rw-r--r--app/assets/javascripts/extensions/element.js.es66
-rw-r--r--app/assets/javascripts/extensions/jquery.js3
-rw-r--r--app/assets/javascripts/files_comment_button.js6
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js.es669
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js.es644
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js.es653
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js.es6126
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_bundle.js7
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6112
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6207
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js.es6230
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es696
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es645
-rw-r--r--app/assets/javascripts/flash.js4
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es630
-rw-r--r--app/assets/javascripts/gl_dropdown.js44
-rw-r--r--app/assets/javascripts/gl_field_errors.js.es63
-rw-r--r--app/assets/javascripts/gl_form.js6
-rw-r--r--app/assets/javascripts/graphs/stat_graph.js4
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors.js4
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_graph.js8
-rw-r--r--app/assets/javascripts/graphs/stat_graph_contributors_util.js5
-rw-r--r--app/assets/javascripts/group_avatar.js8
-rw-r--r--app/assets/javascripts/group_label_subscription.js.es63
-rw-r--r--app/assets/javascripts/groups_select.js4
-rw-r--r--app/assets/javascripts/header.js4
-rw-r--r--app/assets/javascripts/importer_status.js3
-rw-r--r--app/assets/javascripts/issuable.js.es66
-rw-r--r--app/assets/javascripts/issuable/issuable_bundle.js.es61
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es642
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es669
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es613
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/help_state.js.es624
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es611
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es613
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6118
-rw-r--r--app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es661
-rw-r--r--app/assets/javascripts/issuable_context.js13
-rw-r--r--app/assets/javascripts/issuable_form.js8
-rw-r--r--app/assets/javascripts/issue.js6
-rw-r--r--app/assets/javascripts/issue_status_select.js4
-rw-r--r--app/assets/javascripts/issues_bulk_assignment.js.es66
-rw-r--r--app/assets/javascripts/label_manager.js.es64
-rw-r--r--app/assets/javascripts/labels.js6
-rw-r--r--app/assets/javascripts/labels_select.js11
-rw-r--r--app/assets/javascripts/layout_nav.js12
-rw-r--r--app/assets/javascripts/lib/ace.js3
-rw-r--r--app/assets/javascripts/lib/ace/ace_config_paths.js.erb25
-rw-r--r--app/assets/javascripts/lib/utils/animate.js3
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.es6 (renamed from app/assets/javascripts/lib/utils/common_utils.js)109
-rw-r--r--app/assets/javascripts/lib/utils/custom_event_polyfill.js.es612
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js4
-rw-r--r--app/assets/javascripts/lib/utils/notify.js3
-rw-r--r--app/assets/javascripts/lib/utils/pretty_time.js.es626
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js59
-rw-r--r--app/assets/javascripts/lib/utils/type_utility.js3
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js7
-rw-r--r--app/assets/javascripts/lib/vue_resource.js.es62
-rw-r--r--app/assets/javascripts/line_highlighter.js19
-rw-r--r--app/assets/javascripts/logo.js3
-rw-r--r--app/assets/javascripts/member_expiration_date.js.es6 (renamed from app/assets/javascripts/member_expiration_date.js)17
-rw-r--r--app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es610
-rw-r--r--app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es64
-rw-r--r--app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es64
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es65
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es640
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es612
-rw-r--r--app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es63
-rw-r--r--app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es63
-rw-r--r--app/assets/javascripts/merge_request.js6
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.es69
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es618
-rw-r--r--app/assets/javascripts/merge_request_widget/ci_bundle.js.es645
-rw-r--r--app/assets/javascripts/merged_buttons.js6
-rw-r--r--app/assets/javascripts/milestone.js4
-rw-r--r--app/assets/javascripts/milestone_select.js4
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js.es621
-rw-r--r--app/assets/javascripts/namespace_select.js7
-rw-r--r--app/assets/javascripts/network/branch_graph.js20
-rw-r--r--app/assets/javascripts/network/network.js4
-rw-r--r--app/assets/javascripts/network/network_bundle.js3
-rw-r--r--app/assets/javascripts/new_branch_form.js8
-rw-r--r--app/assets/javascripts/new_commit_form.js6
-rw-r--r--app/assets/javascripts/notes.js193
-rw-r--r--app/assets/javascripts/notifications_dropdown.js4
-rw-r--r--app/assets/javascripts/notifications_form.js6
-rw-r--r--app/assets/javascripts/pipelines.js.es65
-rw-r--r--app/assets/javascripts/preview_markdown.js11
-rw-r--r--app/assets/javascripts/profile/gl_crop.js.es69
-rw-r--r--app/assets/javascripts/profile/profile.js.es65
-rw-r--r--app/assets/javascripts/project.js12
-rw-r--r--app/assets/javascripts/project_avatar.js4
-rw-r--r--app/assets/javascripts/project_find_file.js10
-rw-r--r--app/assets/javascripts/project_fork.js4
-rw-r--r--app/assets/javascripts/project_import.js4
-rw-r--r--app/assets/javascripts/project_label_subscription.js.es67
-rw-r--r--app/assets/javascripts/project_new.js36
-rw-r--r--app/assets/javascripts/project_select.js4
-rw-r--r--app/assets/javascripts/project_show.js5
-rw-r--r--app/assets/javascripts/projects_list.js3
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es67
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_create.js.es68
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit.js.es68
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es69
-rw-r--r--app/assets/javascripts/render_gfm.js3
-rw-r--r--app/assets/javascripts/render_math.js3
-rw-r--r--app/assets/javascripts/right_sidebar.js8
-rw-r--r--app/assets/javascripts/search.js10
-rw-r--r--app/assets/javascripts/search_autocomplete.js.es637
-rw-r--r--app/assets/javascripts/shortcuts.js8
-rw-r--r--app/assets/javascripts/shortcuts_blob.js4
-rw-r--r--app/assets/javascripts/shortcuts_dashboard_navigation.js4
-rw-r--r--app/assets/javascripts/shortcuts_find_file.js4
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js58
-rw-r--r--app/assets/javascripts/shortcuts_navigation.js4
-rw-r--r--app/assets/javascripts/shortcuts_network.js4
-rw-r--r--app/assets/javascripts/sidebar.js.es63
-rw-r--r--app/assets/javascripts/single_file_diff.js26
-rw-r--r--app/assets/javascripts/snippet/snippet_bundle.js5
-rw-r--r--app/assets/javascripts/snippets_list.js.es64
-rw-r--r--app/assets/javascripts/star.js4
-rw-r--r--app/assets/javascripts/subbable_resource.js.es63
-rw-r--r--app/assets/javascripts/subscription_select.js4
-rw-r--r--app/assets/javascripts/syntax_highlight.js4
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js.es610
-rw-r--r--app/assets/javascripts/templates/issuable_template_selectors.js.es64
-rw-r--r--app/assets/javascripts/terminal/terminal_bundle.js.es62
-rw-r--r--app/assets/javascripts/todos.js.es65
-rw-r--r--app/assets/javascripts/tree.js4
-rw-r--r--app/assets/javascripts/u2f/authenticate.js.es68
-rw-r--r--app/assets/javascripts/u2f/error.js22
-rw-r--r--app/assets/javascripts/u2f/register.js8
-rw-r--r--app/assets/javascripts/u2f/util.js4
-rw-r--r--app/assets/javascripts/user.js.es64
-rw-r--r--app/assets/javascripts/user_tabs.js.es66
-rw-r--r--app/assets/javascripts/users/calendar.js16
-rw-r--r--app/assets/javascripts/users_select.js12
-rw-r--r--app/assets/javascripts/version_check_image.js.es610
-rw-r--r--app/assets/javascripts/visibility_select.js.es627
-rw-r--r--app/assets/javascripts/vue_pagination/index.js.es6148
-rw-r--r--app/assets/javascripts/vue_pipelines_index/index.js.es642
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6108
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es663
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipelines.js.es6131
-rw-r--r--app/assets/javascripts/vue_pipelines_index/stage.js.es6103
-rw-r--r--app/assets/javascripts/vue_pipelines_index/stages.js.es621
-rw-r--r--app/assets/javascripts/vue_pipelines_index/status.js.es634
-rw-r--r--app/assets/javascripts/vue_pipelines_index/store.js.es665
-rw-r--r--app/assets/javascripts/vue_pipelines_index/time_ago.js.es673
-rw-r--r--app/assets/javascripts/vue_realtime_listener/index.js.es618
-rw-r--r--app/assets/javascripts/zen_mode.js4
-rw-r--r--app/assets/stylesheets/framework/animations.scss74
-rw-r--r--app/assets/stylesheets/framework/avatar.scss6
-rw-r--r--app/assets/stylesheets/framework/awards.scss14
-rw-r--r--app/assets/stylesheets/framework/badges.scss5
-rw-r--r--app/assets/stylesheets/framework/blocks.scss26
-rw-r--r--app/assets/stylesheets/framework/buttons.scss8
-rw-r--r--app/assets/stylesheets/framework/calendar.scss1
-rw-r--r--app/assets/stylesheets/framework/common.scss2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss17
-rw-r--r--app/assets/stylesheets/framework/files.scss49
-rw-r--r--app/assets/stylesheets/framework/filters.scss126
-rw-r--r--app/assets/stylesheets/framework/forms.scss2
-rw-r--r--app/assets/stylesheets/framework/header.scss19
-rw-r--r--app/assets/stylesheets/framework/icons.scss5
-rw-r--r--app/assets/stylesheets/framework/issue_box.scss2
-rw-r--r--app/assets/stylesheets/framework/layout.scss15
-rw-r--r--app/assets/stylesheets/framework/lists.scss53
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss4
-rw-r--r--app/assets/stylesheets/framework/mixins.scss2
-rw-r--r--app/assets/stylesheets/framework/mobile.scss8
-rw-r--r--app/assets/stylesheets/framework/nav.scss6
-rw-r--r--app/assets/stylesheets/framework/page-header.scss8
-rw-r--r--app/assets/stylesheets/framework/panels.scss8
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss10
-rw-r--r--app/assets/stylesheets/framework/timeline.scss4
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap.scss2
-rw-r--r--app/assets/stylesheets/framework/tw_bootstrap_variables.scss9
-rw-r--r--app/assets/stylesheets/framework/typography.scss16
-rw-r--r--app/assets/stylesheets/framework/variables.scss50
-rw-r--r--app/assets/stylesheets/framework/wells.scss2
-rw-r--r--app/assets/stylesheets/framework/zen.scss2
-rw-r--r--app/assets/stylesheets/highlight/dark.scss13
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss12
-rw-r--r--app/assets/stylesheets/highlight/solarized_dark.scss12
-rw-r--r--app/assets/stylesheets/highlight/solarized_light.scss15
-rw-r--r--app/assets/stylesheets/highlight/white.scss18
-rw-r--r--app/assets/stylesheets/notify.scss12
-rw-r--r--app/assets/stylesheets/pages/boards.scss18
-rw-r--r--app/assets/stylesheets/pages/branches.scss55
-rw-r--r--app/assets/stylesheets/pages/builds.scss4
-rw-r--r--app/assets/stylesheets/pages/ci_projects.scss2
-rw-r--r--app/assets/stylesheets/pages/commit.scss132
-rw-r--r--app/assets/stylesheets/pages/commits.scss187
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss22
-rw-r--r--app/assets/stylesheets/pages/detail_page.scss8
-rw-r--r--app/assets/stylesheets/pages/diff.scss6
-rw-r--r--app/assets/stylesheets/pages/environments.scss10
-rw-r--r--app/assets/stylesheets/pages/events.scss2
-rw-r--r--app/assets/stylesheets/pages/groups.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss178
-rw-r--r--app/assets/stylesheets/pages/labels.scss6
-rw-r--r--app/assets/stylesheets/pages/lint.scss10
-rw-r--r--app/assets/stylesheets/pages/login.scss9
-rw-r--r--app/assets/stylesheets/pages/members.scss6
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss44
-rw-r--r--app/assets/stylesheets/pages/milestone.scss6
-rw-r--r--app/assets/stylesheets/pages/note_form.scss20
-rw-r--r--app/assets/stylesheets/pages/notes.scss30
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss530
-rw-r--r--app/assets/stylesheets/pages/profile.scss15
-rw-r--r--app/assets/stylesheets/pages/projects.scss117
-rw-r--r--app/assets/stylesheets/pages/search.scss13
-rw-r--r--app/assets/stylesheets/pages/settings.scss2
-rw-r--r--app/assets/stylesheets/pages/status.scss25
-rw-r--r--app/assets/stylesheets/pages/todos.scss2
-rw-r--r--app/assets/stylesheets/pages/tree.scss29
-rw-r--r--app/assets/stylesheets/pages/wiki.scss4
-rw-r--r--app/controllers/admin/application_settings_controller.rb105
-rw-r--r--app/controllers/admin/groups_controller.rb8
-rw-r--r--app/controllers/admin/users_controller.rb41
-rw-r--r--app/controllers/autocomplete_controller.rb7
-rw-r--r--app/controllers/concerns/cycle_analytics_params.rb4
-rw-r--r--app/controllers/concerns/global_milestones.rb20
-rw-r--r--app/controllers/concerns/service_params.rb79
-rw-r--r--app/controllers/confirmations_controller.rb8
-rw-r--r--app/controllers/dashboard/milestones_controller.rb13
-rw-r--r--app/controllers/explore/projects_controller.rb1
-rw-r--r--app/controllers/groups/milestones_controller.rb11
-rw-r--r--app/controllers/groups_controller.rb8
-rw-r--r--app/controllers/projects/builds_controller.rb2
-rw-r--r--app/controllers/projects/commit_controller.rb5
-rw-r--r--app/controllers/projects/compare_controller.rb11
-rw-r--r--app/controllers/projects/cycle_analytics/events_controller.rb32
-rw-r--r--app/controllers/projects/cycle_analytics_controller.rb54
-rw-r--r--app/controllers/projects/git_http_client_controller.rb12
-rw-r--r--app/controllers/projects/git_http_controller.rb6
-rw-r--r--app/controllers/projects/group_links_controller.rb9
-rw-r--r--app/controllers/projects/hooks_controller.rb14
-rw-r--r--app/controllers/projects/issues_controller.rb12
-rw-r--r--app/controllers/projects/mattermosts_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb17
-rw-r--r--app/controllers/projects/notes_controller.rb3
-rw-r--r--app/controllers/projects/pipelines_controller.rb30
-rw-r--r--app/controllers/projects/project_members_controller.rb52
-rw-r--r--app/controllers/projects/refs_controller.rb6
-rw-r--r--app/controllers/projects/services_controller.rb4
-rw-r--r--app/controllers/projects/settings/integrations_controller.rb18
-rw-r--r--app/controllers/projects/settings/members_controller.rb55
-rw-r--r--app/controllers/projects_controller.rb11
-rw-r--r--app/controllers/registrations_controller.rb18
-rw-r--r--app/controllers/search_controller.rb14
-rw-r--r--app/finders/issuable_finder.rb54
-rw-r--r--app/finders/members_finder.rb13
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/compare_helper.rb2
-rw-r--r--app/helpers/diff_helper.rb6
-rw-r--r--app/helpers/gitlab_markdown_helper.rb44
-rw-r--r--app/helpers/gitlab_routing_helper.rb9
-rw-r--r--app/helpers/groups_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb9
-rw-r--r--app/helpers/issues_helper.rb12
-rw-r--r--app/helpers/merge_requests_helper.rb8
-rw-r--r--app/helpers/projects_helper.rb11
-rw-r--r--app/helpers/search_helper.rb4
-rw-r--r--app/helpers/services_helper.rb20
-rw-r--r--app/helpers/todos_helper.rb3
-rw-r--r--app/helpers/version_check_helper.rb3
-rw-r--r--app/mailers/emails/notes.rb8
-rw-r--r--app/mailers/notify.rb20
-rw-r--r--app/models/ability.rb11
-rw-r--r--app/models/application_setting.rb75
-rw-r--r--app/models/ci/build.rb11
-rw-r--r--app/models/ci/pipeline.rb27
-rw-r--r--app/models/ci/runner.rb33
-rw-r--r--app/models/ci/stage.rb15
-rw-r--r--app/models/commit.rb33
-rw-r--r--app/models/commit_range.rb4
-rw-r--r--app/models/commit_status.rb6
-rw-r--r--app/models/concerns/cache_markdown_field.rb7
-rw-r--r--app/models/concerns/has_status.rb1
-rw-r--r--app/models/concerns/issuable.rb1
-rw-r--r--app/models/concerns/mentionable.rb10
-rw-r--r--app/models/concerns/milestoneish.rb17
-rw-r--r--app/models/concerns/participable.rb7
-rw-r--r--app/models/concerns/presentable.rb7
-rw-r--r--app/models/concerns/project_features_compatibility.rb2
-rw-r--r--app/models/concerns/reactive_caching.rb46
-rw-r--r--app/models/concerns/reactive_service.rb10
-rw-r--r--app/models/concerns/referable.rb2
-rw-r--r--app/models/concerns/routable.rb15
-rw-r--r--app/models/concerns/taskable.rb8
-rw-r--r--app/models/concerns/time_trackable.rb72
-rw-r--r--app/models/concerns/valid_attribute.rb10
-rw-r--r--app/models/cycle_analytics.rb60
-rw-r--r--app/models/cycle_analytics/summary.rb43
-rw-r--r--app/models/dashboard_milestone.rb5
-rw-r--r--app/models/environment.rb2
-rw-r--r--app/models/external_issue.rb2
-rw-r--r--app/models/forked_project_link.rb4
-rw-r--r--app/models/generic_commit_status.rb10
-rw-r--r--app/models/global_milestone.rb19
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/group_label.rb4
-rw-r--r--app/models/group_milestone.rb19
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/key.rb9
-rw-r--r--app/models/label.rb11
-rw-r--r--app/models/member.rb20
-rw-r--r--app/models/merge_request.rb32
-rw-r--r--app/models/merge_request_diff.rb22
-rw-r--r--app/models/milestone.rb8
-rw-r--r--app/models/namespace.rb47
-rw-r--r--app/models/note.rb19
-rw-r--r--app/models/notification_setting.rb4
-rw-r--r--app/models/project.rb45
-rw-r--r--app/models/project_group_link.rb3
-rw-r--r--app/models/project_label.rb4
-rw-r--r--app/models/project_services/asana_service.rb4
-rw-r--r--app/models/project_services/assembla_service.rb4
-rw-r--r--app/models/project_services/bamboo_service.rb49
-rw-r--r--app/models/project_services/bugzilla_service.rb2
-rw-r--r--app/models/project_services/buildkite_service.rb29
-rw-r--r--app/models/project_services/builds_email_service.rb4
-rw-r--r--app/models/project_services/campfire_service.rb4
-rw-r--r--app/models/project_services/chat_notification_service.rb16
-rw-r--r--app/models/project_services/chat_slash_commands_service.rb4
-rw-r--r--app/models/project_services/ci_service.rb29
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/deployment_service.rb4
-rw-r--r--app/models/project_services/drone_ci_service.rb80
-rw-r--r--app/models/project_services/emails_on_push_service.rb4
-rw-r--r--app/models/project_services/external_wiki_service.rb6
-rw-r--r--app/models/project_services/flowdock_service.rb4
-rw-r--r--app/models/project_services/gemnasium_service.rb4
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb2
-rw-r--r--app/models/project_services/hipchat_service.rb4
-rw-r--r--app/models/project_services/irker_service.rb4
-rw-r--r--app/models/project_services/issue_tracker_service.rb2
-rw-r--r--app/models/project_services/jira_service.rb4
-rw-r--r--app/models/project_services/kubernetes_service.rb2
-rw-r--r--app/models/project_services/mattermost_service.rb4
-rw-r--r--app/models/project_services/mattermost_slash_commands_service.rb2
-rw-r--r--app/models/project_services/pipelines_email_service.rb4
-rw-r--r--app/models/project_services/pivotaltracker_service.rb4
-rw-r--r--app/models/project_services/pushover_service.rb4
-rw-r--r--app/models/project_services/redmine_service.rb2
-rw-r--r--app/models/project_services/slack_service.rb2
-rw-r--r--app/models/project_services/slack_slash_commands_service.rb2
-rw-r--r--app/models/project_services/teamcity_service.rb79
-rw-r--r--app/models/project_statistics.rb3
-rw-r--r--app/models/repository.rb18
-rw-r--r--app/models/route.rb7
-rw-r--r--app/models/service.rb15
-rw-r--r--app/models/snippet.rb4
-rw-r--r--app/models/timelog.rb6
-rw-r--r--app/models/todo.rb8
-rw-r--r--app/models/user.rb20
-rw-r--r--app/policies/base_policy.rb4
-rw-r--r--app/presenters/README.md154
-rw-r--r--app/presenters/ci/build_presenter.rb15
-rw-r--r--app/serializers/analytics_stage_entity.rb10
-rw-r--r--app/serializers/analytics_stage_serializer.rb3
-rw-r--r--app/serializers/analytics_summary_entity.rb7
-rw-r--r--app/serializers/analytics_summary_serializer.rb3
-rw-r--r--app/serializers/build_action_entity.rb14
-rw-r--r--app/serializers/build_artifact_entity.rb14
-rw-r--r--app/serializers/commit_entity.rb12
-rw-r--r--app/serializers/issuable_entity.rb4
-rw-r--r--app/serializers/pipeline_entity.rb85
-rw-r--r--app/serializers/pipeline_serializer.rb40
-rw-r--r--app/serializers/request_aware_entity.rb9
-rw-r--r--app/serializers/stage_entity.rb38
-rw-r--r--app/serializers/status_entity.rb8
-rw-r--r--app/services/application_settings/base_service.rb7
-rw-r--r--app/services/application_settings/update_service.rb7
-rw-r--r--app/services/ci/register_build_service.rb62
-rw-r--r--app/services/ci/update_build_queue_service.rb19
-rw-r--r--app/services/issuable_base_service.rb16
-rw-r--r--app/services/merge_requests/base_service.rb16
-rw-r--r--app/services/merge_requests/refresh_service.rb41
-rw-r--r--app/services/merge_requests/update_service.rb15
-rw-r--r--app/services/notes/create_service.rb12
-rw-r--r--app/services/notes/post_process_service.rb3
-rw-r--r--app/services/notes/slash_commands_service.rb6
-rw-r--r--app/services/notification_service.rb27
-rw-r--r--app/services/projects/create_service.rb26
-rw-r--r--app/services/projects/participants_service.rb2
-rw-r--r--app/services/projects/update_service.rb8
-rw-r--r--app/services/slash_commands/interpret_service.rb60
-rw-r--r--app/services/system_note_service.rb57
-rw-r--r--app/services/todo_service.rb30
-rw-r--r--app/services/user_project_access_changed_service.rb2
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb48
-rw-r--r--app/uploaders/avatar_uploader.rb11
-rw-r--r--app/validators/project_path_validator.rb2
-rw-r--r--app/views/admin/application_settings/_form.html.haml17
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml2
-rw-r--r--app/views/admin/identities/_identity.html.haml2
-rw-r--r--app/views/admin/runners/index.html.haml2
-rw-r--r--app/views/admin/runners/show.html.haml2
-rw-r--r--app/views/admin/system_info/show.html.haml10
-rw-r--r--app/views/admin/users/show.html.haml2
-rw-r--r--app/views/award_emoji/_awards_block.html.haml3
-rw-r--r--app/views/ci/lints/show.html.haml31
-rw-r--r--app/views/ci/status/_badge.html.haml3
-rw-r--r--app/views/ci/status/_dropdown_graph_badge.html.haml19
-rw-r--r--app/views/dashboard/todos/_todo.html.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml3
-rw-r--r--app/views/discussions/_discussion.html.haml3
-rw-r--r--app/views/discussions/_parallel_diff_discussion.html.haml4
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml2
-rw-r--r--app/views/groups/group_members/index.html.haml2
-rw-r--r--app/views/groups/issues.html.haml2
-rw-r--r--app/views/groups/merge_requests.html.haml2
-rw-r--r--app/views/groups/milestones/index.html.haml2
-rw-r--r--app/views/groups/new.html.haml2
-rw-r--r--app/views/import/_githubish_status.html.haml2
-rw-r--r--app/views/import/fogbugz/new_user_map.html.haml2
-rw-r--r--app/views/import/fogbugz/status.html.haml2
-rw-r--r--app/views/import/google_code/status.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml4
-rw-r--r--app/views/layouts/nav/_project.html.haml4
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml19
-rw-r--r--app/views/layouts/profile.html.haml4
-rw-r--r--app/views/notify/_reassigned_issuable_email.html.haml4
-rw-r--r--app/views/notify/closed_issue_email.html.haml2
-rw-r--r--app/views/notify/closed_issue_email.text.haml2
-rw-r--r--app/views/notify/closed_merge_request_email.html.haml2
-rw-r--r--app/views/notify/closed_merge_request_email.text.haml2
-rw-r--r--app/views/notify/issue_status_changed_email.html.haml2
-rw-r--r--app/views/notify/merge_request_status_email.html.haml2
-rw-r--r--app/views/notify/merge_request_status_email.text.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.html.haml2
-rw-r--r--app/views/notify/merged_merge_request_email.text.haml2
-rw-r--r--app/views/notify/note_personal_snippet_email.html.haml1
-rw-r--r--app/views/notify/note_personal_snippet_email.text.erb8
-rw-r--r--app/views/notify/pipeline_failed_email.html.haml2
-rw-r--r--app/views/notify/pipeline_success_email.html.haml4
-rw-r--r--app/views/notify/project_was_not_exported_email.text.haml4
-rw-r--r--app/views/notify/repository_push_email.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml4
-rw-r--r--app/views/profiles/keys/_key.html.haml3
-rw-r--r--app/views/profiles/keys/_key_details.html.haml3
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml2
-rw-r--r--app/views/profiles/show.html.haml5
-rw-r--r--app/views/projects/_md_preview.html.haml2
-rw-r--r--app/views/projects/_merge_request_merge_settings.html.haml2
-rw-r--r--app/views/projects/_visibility_select.html.haml7
-rw-r--r--app/views/projects/blob/_blob.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml2
-rw-r--r--app/views/projects/blob/_image.html.haml4
-rw-r--r--app/views/projects/blob/_upload.html.haml2
-rw-r--r--app/views/projects/boards/components/_sidebar.html.haml43
-rw-r--r--app/views/projects/branches/_branch.html.haml3
-rw-r--r--app/views/projects/branches/index.html.haml5
-rw-r--r--app/views/projects/builds/_header.html.haml4
-rw-r--r--app/views/projects/builds/_sidebar.html.haml4
-rw-r--r--app/views/projects/builds/show.html.haml6
-rw-r--r--app/views/projects/buttons/_download.html.haml2
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml35
-rw-r--r--app/views/projects/commit/_change.html.haml2
-rw-r--r--app/views/projects/commit/_ci_menu.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml5
-rw-r--r--app/views/projects/commit/_pipelines_list.haml6
-rw-r--r--app/views/projects/commit/pipelines.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml46
-rw-r--r--app/views/projects/commits/_commit_list.html.haml2
-rw-r--r--app/views/projects/commits/_commits.html.haml4
-rw-r--r--app/views/projects/commits/show.html.haml7
-rw-r--r--app/views/projects/compare/_form.html.haml10
-rw-r--r--app/views/projects/compare/show.html.haml4
-rw-r--r--app/views/projects/cycle_analytics/_empty_stage.html.haml2
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml4
-rw-r--r--app/views/projects/deployments/_deployment.html.haml4
-rw-r--r--app/views/projects/diffs/_content.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml4
-rw-r--r--app/views/projects/diffs/_file.html.haml2
-rw-r--r--app/views/projects/diffs/_file_header.html.haml2
-rw-r--r--app/views/projects/diffs/_image.html.haml6
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml4
-rw-r--r--app/views/projects/diffs/_stats.html.haml2
-rw-r--r--app/views/projects/edit.html.haml125
-rw-r--r--app/views/projects/empty.html.haml11
-rw-r--r--app/views/projects/forks/index.html.haml2
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml2
-rw-r--r--app/views/projects/graphs/commits.html.haml10
-rw-r--r--app/views/projects/group_links/_index.html.haml (renamed from app/views/projects/group_links/index.html.haml)4
-rw-r--r--app/views/projects/hooks/_index.html.haml (renamed from app/views/projects/hooks/index.html.haml)0
-rw-r--r--app/views/projects/issues/index.html.haml10
-rw-r--r--app/views/projects/issues/show.html.haml4
-rw-r--r--app/views/projects/mattermosts/_team_selection.html.haml11
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml8
-rw-r--r--app/views/projects/merge_requests/_show.html.haml102
-rw-r--r--app/views/projects/merge_requests/conflicts.html.haml1
-rw-r--r--app/views/projects/merge_requests/show/_commits.html.haml10
-rw-r--r--app/views/projects/merge_requests/show/_diffs.html.haml10
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml6
-rw-r--r--app/views/projects/merge_requests/show/_versions.html.haml10
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml4
-rw-r--r--app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml3
-rw-r--r--app/views/projects/notes/_edit_form.html.haml13
-rw-r--r--app/views/projects/notes/_form.html.haml1
-rw-r--r--app/views/projects/notes/_note.html.haml4
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml5
-rw-r--r--app/views/projects/pipelines/_stage.html.haml7
-rw-r--r--app/views/projects/pipelines/index.html.haml40
-rw-r--r--app/views/projects/project_members/_group_members.html.haml2
-rw-r--r--app/views/projects/project_members/_groups.html.haml2
-rw-r--r--app/views/projects/project_members/_index.html.haml22
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml38
-rw-r--r--app/views/projects/project_members/_shared_group_members.html.haml4
-rw-r--r--app/views/projects/project_members/_team.html.haml10
-rw-r--r--app/views/projects/project_members/import.html.haml3
-rw-r--r--app/views/projects/project_members/index.html.haml29
-rw-r--r--app/views/projects/releases/edit.html.haml2
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml4
-rw-r--r--app/views/projects/services/_form.html.haml2
-rw-r--r--app/views/projects/services/_index.html.haml (renamed from app/views/projects/services/index.html.haml)2
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_help.html.haml4
-rw-r--r--app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml2
-rw-r--r--app/views/projects/services/slack_slash_commands/_help.html.haml144
-rw-r--r--app/views/projects/settings/integrations/_project_hook.html.haml (renamed from app/views/projects/hooks/_project_hook.html.haml)0
-rw-r--r--app/views/projects/settings/integrations/show.html.haml3
-rw-r--r--app/views/projects/settings/members/show.html.haml6
-rw-r--r--app/views/projects/stage/_graph.html.haml2
-rw-r--r--app/views/projects/stage/_in_stage_group.html.haml9
-rw-r--r--app/views/projects/tree/_tree_content.html.haml4
-rw-r--r--app/views/projects/wikis/git_access.html.haml7
-rw-r--r--app/views/search/results/_empty.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml2
-rw-r--r--app/views/search/results/_snippet_title.html.haml2
-rw-r--r--app/views/shared/_choose_group_avatar_button.html.haml2
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--app/views/shared/_confirm_modal.html.haml2
-rw-r--r--app/views/shared/_milestones_filter.html.haml6
-rw-r--r--app/views/shared/_no_password.html.haml4
-rw-r--r--app/views/shared/_no_ssh.html.haml4
-rw-r--r--app/views/shared/groups/_group.html.haml2
-rw-r--r--app/views/shared/icons/_icon_stopwatch.svg1
-rw-r--r--app/views/shared/issuable/_form.html.haml5
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml133
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml20
-rw-r--r--app/views/shared/issuable/form/_title.html.haml4
-rw-r--r--app/views/shared/members/_group.html.haml1
-rw-r--r--app/views/shared/milestones/_issuable.html.haml2
-rw-r--r--app/views/shared/milestones/_issuables.html.haml2
-rw-r--r--app/views/shared/milestones/_milestone.html.haml2
-rw-r--r--app/views/shared/tokens/_scopes_form.html.haml4
-rw-r--r--app/views/shared/web_hooks/_form.html.haml3
-rw-r--r--app/views/users/calendar_activities.html.haml2
-rw-r--r--app/views/users/show.html.haml8
-rw-r--r--app/workers/authorized_projects_worker.rb7
-rw-r--r--app/workers/build_queue_worker.rb10
-rw-r--r--app/workers/reactive_caching_worker.rb4
-rw-r--r--app/workers/use_key_worker.rb13
631 files changed, 9330 insertions, 3377 deletions
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index 5a7d823e84c..993f427c9fb 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */
/* global Turbolinks */
(function() {
@@ -61,7 +61,5 @@
}
return Admin;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index f60f27d1210..b4a8c827d7f 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, indent, object-curly-spacing, quote-props, no-param-reassign, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, quote-props, no-param-reassign, max-len */
(function() {
var Api = {
@@ -29,9 +29,9 @@
return $.ajax({
url: url,
data: $.extend({
- search: query,
- per_page: 20
- }, options),
+ search: query,
+ per_page: 20
+ }, options),
dataType: "json"
}).done(function(groups) {
return callback(groups);
@@ -73,7 +73,7 @@
return $.ajax({
url: url,
type: "POST",
- data: {'label': data},
+ data: { 'label': data },
dataType: "json"
}).done(function(label) {
return callback(label);
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index e43afbb4cc9..4849aab50f4 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -58,6 +58,7 @@
/*= require_directory ./extensions */
/*= require_directory ./lib/utils */
/*= require_directory ./u2f */
+/*= require_directory ./droplab */
/*= require_directory . */
/*= require fuzzaldrin-plus */
/*= require es6-promise.auto */
@@ -83,7 +84,6 @@
var $sidebarGutterToggle = $('.js-sidebar-toggle');
var $flash = $('.flash-container');
var bootstrapBreakpoint = bp.getBreakpointSize();
- var checkInitialSidebarSize;
var fitSidebarForSize;
// Set the default path for all cookies to GitLab's root directory
@@ -245,19 +245,11 @@
return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
}
};
- checkInitialSidebarSize = function () {
- bootstrapBreakpoint = bp.getBreakpointSize();
- if (bootstrapBreakpoint === 'xs' || 'sm') {
- return $document.trigger('breakpoint:change', [bootstrapBreakpoint]);
- }
- };
$window.off('resize.app').on('resize.app', function () {
return fitSidebarForSize();
});
gl.awardsHandler = new AwardsHandler();
- checkInitialSidebarSize();
new Aside();
-
// bind sidebar events
new gl.Sidebar();
});
diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js
index 9417afc2ea7..8438de6cdf1 100644
--- a/app/assets/javascripts/aside.js
+++ b/app/assets/javascripts/aside.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, max-len */
(function() {
this.Aside = (function() {
function Aside() {
@@ -21,7 +21,5 @@
}
return Aside;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js
index f45dbe4cbf2..b16a2c0f73a 100644
--- a/app/assets/javascripts/autosave.js
+++ b/app/assets/javascripts/autosave.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, max-len */
(function() {
this.Autosave = (function() {
function Autosave(field, key) {
@@ -58,7 +58,5 @@
};
return Autosave;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index 107a7978a87..629dc267337 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -1,9 +1,9 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, spaced-comment, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, radix, keyword-spacing, space-before-blocks, brace-style, no-underscore-dangle, no-plusplus, no-return-assign, camelcase, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, brace-style, no-underscore-dangle, no-return-assign, camelcase */
/* global Cookies */
(function() {
this.AwardsHandler = (function() {
- var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; //For separating lists produced by ruby's Array#toSentence
+ var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
function AwardsHandler() {
this.aliases = gl.emojiAliases();
$(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
@@ -134,7 +134,7 @@
return this.decrementCounter($emojiButton, emoji);
} else {
counter = $emojiButton.find('.js-counter');
- counter.text(parseInt(counter.text()) + 1);
+ counter.text(parseInt(counter.text(), 10) + 1);
$emojiButton.addClass('active');
this.addYouToUserList(votesBlock, emoji);
return this.animateEmoji($emojiButton);
@@ -211,10 +211,10 @@
};
AwardsHandler.prototype.toSentence = function(list) {
- if(list.length <= 2){
+ if (list.length <= 2) {
return list.join(' and ');
}
- else{
+ else {
return list.slice(0, -1).join(', ') + ', and ' + list[list.length - 1];
}
};
@@ -339,7 +339,7 @@
if (Cookies.get('frequently_used_emojis')) {
frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
ul = $("<ul class='clearfix emoji-menu-list frequent-emojis'>");
- for (i = 0, len = frequentlyUsedEmojis.length; i < len; i++) {
+ for (i = 0, len = frequentlyUsedEmojis.length; i < len; i += 1) {
emoji = frequentlyUsedEmojis[i];
$(".emoji-menu-content [data-emoji='" + emoji + "']").closest('li').clone().appendTo(ul);
}
@@ -374,7 +374,5 @@
};
return AwardsHandler;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js
index c62a4c5a456..a6bc262b657 100644
--- a/app/assets/javascripts/behaviors/autosize.js
+++ b/app/assets/javascripts/behaviors/autosize.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
/* global autosize */
/*= require jquery.ba-resize */
@@ -26,5 +26,4 @@
autosize.update($fields);
return $fields.css('resize', 'vertical');
});
-
}).call(this);
diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js
index 3998ee9a0a0..6af8f593872 100644
--- a/app/assets/javascripts/behaviors/details_behavior.js
+++ b/app/assets/javascripts/behaviors/details_behavior.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, max-len */
(function() {
$(function() {
$("body").on("click", ".js-details-target", function() {
@@ -23,5 +23,4 @@
return e.preventDefault();
});
});
-
}).call(this);
diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js
index 586f941a6e3..d4895011be7 100644
--- a/app/assets/javascripts/behaviors/quick_submit.js
+++ b/app/assets/javascripts/behaviors/quick_submit.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, camelcase, consistent-return, quotes, object-shorthand, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, camelcase, consistent-return, quotes, object-shorthand, comma-dangle, max-len */
// Quick Submit behavior
//
@@ -74,5 +74,4 @@
return $this.tooltip('hide');
});
});
-
}).call(this);
diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js
index 72362988b2e..ccbd6b993cb 100644
--- a/app/assets/javascripts/behaviors/requires_input.js
+++ b/app/assets/javascripts/behaviors/requires_input.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, no-else-return, consistent-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, no-else-return, consistent-return, max-len */
// Requires Input behavior
//
// When called on a form with input fields with the `required` attribute, the
@@ -59,5 +59,4 @@
return hideOrShowHelpBlock($form);
});
});
-
}).call(this);
diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js.es6
index 57bd13eecf8..d3455fa3d8c 100644
--- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6
+++ b/app/assets/javascripts/blob/blob_ci_yaml.js.es6
@@ -1,9 +1,8 @@
-/* eslint-disable padded-blocks, no-param-reassign, comma-dangle */
+/* eslint-disable no-param-reassign, comma-dangle */
/* global Api */
/*= require blob/template_selector */
((global) => {
-
class BlobCiYamlSelector extends gl.TemplateSelector {
requestFile(query) {
return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this));
@@ -39,5 +38,4 @@
}
global.BlobCiYamlSelectors = BlobCiYamlSelectors;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js
index eab686c45c3..04bfe363929 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, camelcase, object-shorthand, quotes, comma-dangle, prefer-arrow-callback, no-unused-vars, prefer-template, no-useless-escape, no-alert, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, camelcase, object-shorthand, quotes, comma-dangle, prefer-arrow-callback, no-unused-vars, prefer-template, no-useless-escape, no-alert, max-len */
/* global Dropzone */
(function() {
@@ -62,7 +62,5 @@
}
return BlobFileDropzone;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js
index 15563e429a0..5fd0857db29 100644
--- a/app/assets/javascripts/blob/blob_gitignore_selector.js
+++ b/app/assets/javascripts/blob/blob_gitignore_selector.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params */
/* global Api */
/*= require blob/template_selector */
@@ -19,7 +19,5 @@
};
return BlobGitignoreSelector;
-
})(gl.TemplateSelector);
-
}).call(this);
diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js b/app/assets/javascripts/blob/blob_gitignore_selectors.js
index d7f95093688..8236457f0f1 100644
--- a/app/assets/javascripts/blob/blob_gitignore_selectors.js
+++ b/app/assets/javascripts/blob/blob_gitignore_selectors.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-cond-assign, no-sequences, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-cond-assign, no-sequences, comma-dangle, max-len */
/* global BlobGitignoreSelector */
(function() {
@@ -22,7 +22,5 @@
}
return BlobGitignoreSelectors;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js
index d9c6f65a083..7a14eb160d0 100644
--- a/app/assets/javascripts/blob/blob_license_selector.js
+++ b/app/assets/javascripts/blob/blob_license_selector.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle */
/* global Api */
/*= require blob/template_selector */
@@ -24,7 +24,5 @@
};
return BlobLicenseSelector;
-
})(gl.TemplateSelector);
-
}).call(this);
diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js.es6
index 268640681d4..c5067b0feae 100644
--- a/app/assets/javascripts/blob/blob_license_selectors.js.es6
+++ b/app/assets/javascripts/blob/blob_license_selectors.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-unused-vars, no-param-reassign, padded-blocks */
+/* eslint-disable no-unused-vars, no-param-reassign */
/* global BlobLicenseSelector */
((global) => {
@@ -20,5 +20,4 @@
}
global.BlobLicenseSelectors = BlobLicenseSelectors;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6
index 7a1ee9998c8..7e03ec3b391 100644
--- a/app/assets/javascripts/blob/template_selector.js.es6
+++ b/app/assets/javascripts/blob/template_selector.js.es6
@@ -1,101 +1,101 @@
-/* eslint-disable indent, comma-dangle, object-shorthand, func-names, space-before-function-paren, arrow-parens, no-unused-vars, class-methods-use-this, no-var, consistent-return, prefer-const, no-param-reassign, space-in-parens, max-len */
+/* eslint-disable comma-dangle, object-shorthand, func-names, space-before-function-paren, arrow-parens, no-unused-vars, class-methods-use-this, no-var, consistent-return, no-param-reassign, max-len */
((global) => {
- class TemplateSelector {
- constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) {
- this.onClick = this.onClick.bind(this);
- this.dropdown = dropdown;
- this.data = data;
- this.pattern = pattern;
- this.wrapper = wrapper;
- this.editor = editor;
- this.fileEndpoint = fileEndpoint;
- this.$input = $input || $('#file_name');
- this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
- this.buildDropdown();
- this.bindEvents();
- this.onFilenameUpdate();
+ class TemplateSelector {
+ constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) {
+ this.onClick = this.onClick.bind(this);
+ this.dropdown = dropdown;
+ this.data = data;
+ this.pattern = pattern;
+ this.wrapper = wrapper;
+ this.editor = editor;
+ this.fileEndpoint = fileEndpoint;
+ this.$input = $input || $('#file_name');
+ this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
+ this.buildDropdown();
+ this.bindEvents();
+ this.onFilenameUpdate();
- this.autosizeUpdateEvent = document.createEvent('Event');
- this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
- }
+ this.autosizeUpdateEvent = document.createEvent('Event');
+ this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
+ }
- buildDropdown() {
- return this.dropdown.glDropdown({
- data: this.data,
- filterable: true,
- selectable: true,
- toggleLabel: this.toggleLabel,
- search: {
- fields: ['name']
- },
- clicked: this.onClick,
- text: function(item) {
- return item.name;
- }
- });
- }
+ buildDropdown() {
+ return this.dropdown.glDropdown({
+ data: this.data,
+ filterable: true,
+ selectable: true,
+ toggleLabel: this.toggleLabel,
+ search: {
+ fields: ['name']
+ },
+ clicked: this.onClick,
+ text: function(item) {
+ return item.name;
+ }
+ });
+ }
- bindEvents() {
- return this.$input.on('keyup blur', (e) => this.onFilenameUpdate());
- }
+ bindEvents() {
+ return this.$input.on('keyup blur', (e) => this.onFilenameUpdate());
+ }
- toggleLabel(item) {
- return item.name;
- }
+ toggleLabel(item) {
+ return item.name;
+ }
- onFilenameUpdate() {
- var filenameMatches;
- if (!this.$input.length) {
- return;
- }
- filenameMatches = this.pattern.test(this.$input.val().trim());
- if (!filenameMatches) {
- this.wrapper.addClass('hidden');
- return;
- }
- return this.wrapper.removeClass('hidden');
+ onFilenameUpdate() {
+ var filenameMatches;
+ if (!this.$input.length) {
+ return;
}
-
- onClick(item, el, e) {
- e.preventDefault();
- return this.requestFile(item);
+ filenameMatches = this.pattern.test(this.$input.val().trim());
+ if (!filenameMatches) {
+ this.wrapper.addClass('hidden');
+ return;
}
+ return this.wrapper.removeClass('hidden');
+ }
- requestFile(item) {
- // This `requestFile` method is an abstract method that should
- // be added by all subclasses.
- }
+ onClick(item, el, e) {
+ e.preventDefault();
+ return this.requestFile(item);
+ }
- // To be implemented on the extending class
- // e.g.
- // Api.gitignoreText item.name, @requestFileSuccess.bind(@)
- requestFileSuccess(file, { skipFocus } = {}) {
- if (!file) return;
+ requestFile(item) {
+ // This `requestFile` method is an abstract method that should
+ // be added by all subclasses.
+ }
- const oldValue = this.editor.getValue();
- let newValue = file.content;
+ // To be implemented on the extending class
+ // e.g.
+ // Api.gitignoreText item.name, @requestFileSuccess.bind(@)
+ requestFileSuccess(file, { skipFocus } = {}) {
+ if (!file) return;
- this.editor.setValue(newValue, 1);
- if (!skipFocus) this.editor.focus();
+ const oldValue = this.editor.getValue();
+ const newValue = file.content;
- if (this.editor instanceof jQuery) {
- this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
- }
- }
+ this.editor.setValue(newValue, 1);
+ if (!skipFocus) this.editor.focus();
- startLoadingSpinner() {
- this.dropdownIcon
- .addClass('fa-spinner fa-spin')
- .removeClass('fa-chevron-down');
+ if (this.editor instanceof jQuery) {
+ this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
+ }
- stopLoadingSpinner() {
- this.dropdownIcon
- .addClass('fa-chevron-down')
- .removeClass('fa-spinner fa-spin');
- }
+ startLoadingSpinner() {
+ this.dropdownIcon
+ .addClass('fa-spinner fa-spin')
+ .removeClass('fa-chevron-down');
+ }
+
+ stopLoadingSpinner() {
+ this.dropdownIcon
+ .addClass('fa-chevron-down')
+ .removeClass('fa-spinner fa-spin');
}
+ }
- global.TemplateSelector = TemplateSelector;
- })(window.gl || ( window.gl = {}));
+ global.TemplateSelector = TemplateSelector;
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/blob_edit/blob_edit_bundle.js b/app/assets/javascripts/blob_edit/blob_edit_bundle.js
index 8c40e36a80a..dfad9b2122b 100644
--- a/app/assets/javascripts/blob_edit/blob_edit_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_edit_bundle.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, vars-on-top, no-unused-vars, no-new, max-len */
/* global EditBlob */
/* global NewCommitForm */
@@ -12,5 +12,4 @@
var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language'));
new NewCommitForm($('.js-edit-blob-form'));
});
-
}).call(this);
diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js
index fa43ff611cc..079445e8278 100644
--- a/app/assets/javascripts/blob_edit/edit_blob.js
+++ b/app/assets/javascripts/blob_edit/edit_blob.js
@@ -1,9 +1,9 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, camelcase, no-param-reassign, quotes, prefer-template, no-new, comma-dangle, one-var, one-var-declaration-per-line, prefer-arrow-callback, no-else-return, no-unused-vars, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, no-param-reassign, quotes, prefer-template, no-new, comma-dangle, one-var, one-var-declaration-per-line, prefer-arrow-callback, no-else-return, no-unused-vars, max-len */
/* global ace */
/* global BlobGitignoreSelectors */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.EditBlob = (function() {
function EditBlob(assets_path, ace_mode) {
@@ -84,7 +84,5 @@
};
return EditBlob;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6
index 2c9ab61c94d..f9766471780 100644
--- a/app/assets/javascripts/boards/boards_bundle.js.es6
+++ b/app/assets/javascripts/boards/boards_bundle.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable one-var, indent, quote-props, comma-dangle, space-before-function-paren */
+/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */
/* global Vue */
/* global BoardService */
@@ -16,8 +16,8 @@
//= require ./vue_resource_interceptor
$(() => {
- const $boardApp = document.getElementById('board-app'),
- Store = gl.issueBoards.BoardsStore;
+ const $boardApp = document.getElementById('board-app');
+ const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js.es6
index d1fb0ec48e0..a32881116d5 100644
--- a/app/assets/javascripts/boards/components/board.js.es6
+++ b/app/assets/javascripts/boards/components/board.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, space-before-function-paren, one-var, indent, radix */
+/* eslint-disable comma-dangle, space-before-function-paren, one-var */
/* global Vue */
/* global Sortable */
@@ -45,14 +45,28 @@
const issue = this.list.findIssue(this.detailIssue.issue.id);
if (issue) {
+ const offsetLeft = this.$el.offsetLeft;
const boardsList = document.querySelectorAll('.boards-list')[0];
- const right = (this.$el.offsetLeft + this.$el.offsetWidth) - boardsList.offsetWidth;
- const left = boardsList.scrollLeft - this.$el.offsetLeft;
+ const left = boardsList.scrollLeft - offsetLeft;
+ let right = (offsetLeft + this.$el.offsetWidth);
+
+ if (window.innerWidth > 768 && boardsList.classList.contains('is-compact')) {
+ // -290 here because width of boardsList is animating so therefore
+ // getting the width here is incorrect
+ // 290 is the width of the sidebar
+ right -= (boardsList.offsetWidth - 290);
+ } else {
+ right -= boardsList.offsetWidth;
+ }
if (right - boardsList.scrollLeft > 0) {
- boardsList.scrollLeft = right;
+ $(boardsList).animate({
+ scrollLeft: right
+ }, this.sortableOptions.animation);
} else if (left > 0) {
- boardsList.scrollLeft = this.$el.offsetLeft;
+ $(boardsList).animate({
+ scrollLeft: offsetLeft
+ }, this.sortableOptions.animation);
}
}
},
@@ -65,7 +79,7 @@
}
},
mounted () {
- const options = gl.issueBoards.getBoardSortableDefaultOptions({
+ this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
disabled: this.disabled,
group: 'boards',
draggable: '.is-draggable',
@@ -74,8 +88,8 @@
gl.issueBoards.onEnd();
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
- const order = this.sortable.toArray(),
- list = Store.findList('id', parseInt(e.item.dataset.id));
+ const order = this.sortable.toArray();
+ const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
this.$nextTick(() => {
Store.moveList(list, order);
@@ -84,7 +98,7 @@
}
});
- this.sortable = Sortable.create(this.$el.parentNode, options);
+ this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
},
});
})();
diff --git a/app/assets/javascripts/boards/components/board_blank_state.js.es6 b/app/assets/javascripts/boards/components/board_blank_state.js.es6
index 0a47a22fad2..d76314c1892 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.js.es6
+++ b/app/assets/javascripts/boards/components/board_blank_state.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, comma-dangle, semi */
+/* eslint-disable space-before-function-paren, comma-dangle */
/* global Vue */
/* global ListLabel */
@@ -15,7 +15,7 @@
new ListLabel({ title: 'To Do', color: '#F0AD4E' }),
new ListLabel({ title: 'Doing', color: '#5CB85C' })
]
- }
+ };
},
methods: {
addDefaultLists () {
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index 6711930622b..630fe084175 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, space-before-function-paren, max-len, no-plusplus */
+/* eslint-disable comma-dangle, space-before-function-paren, max-len */
/* global Vue */
/* global Sortable */
@@ -43,7 +43,7 @@
issues () {
this.$nextTick(() => {
if (this.scrollHeight() <= this.listHeight() && this.list.issuesSize > this.list.issues.length) {
- this.list.page++;
+ this.list.page += 1;
this.list.getIssues(false);
}
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6
index 3f5cf8420a8..556826a9148 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var, indent */
+/* eslint-disable comma-dangle, func-names, no-new, space-before-function-paren, one-var */
(() => {
window.gl = window.gl || {};
@@ -32,17 +32,17 @@
});
},
renderRow (label) {
- const active = Store.findList('title', label.title),
- $li = $('<li />'),
- $a = $('<a />', {
- class: (active ? `is-active js-board-list-${active.id}` : ''),
- text: label.title,
- href: '#'
- }),
- $labelColor = $('<span />', {
- class: 'dropdown-label-box',
- style: `background-color: ${label.color}`
- });
+ const active = Store.findList('title', label.title);
+ const $li = $('<li />');
+ const $a = $('<a />', {
+ class: (active ? `is-active js-board-list-${active.id}` : ''),
+ text: label.title,
+ href: '#'
+ });
+ const $labelColor = $('<span />', {
+ class: 'dropdown-label-box',
+ style: `background-color: ${label.color}`
+ });
return $li.append($a.prepend($labelColor));
},
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
index a5e62ed775d..b6c6d17274f 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-unused-vars, no-mixed-operators, prefer-const, comma-dangle, semi */
+/* eslint-disable no-unused-vars, no-mixed-operators, comma-dangle */
/* global DocumentTouch */
((w) => {
@@ -19,7 +19,8 @@
gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
- let defaultSortOptions = {
+ const defaultSortOptions = {
+ animation: 200,
forceFallback: true,
fallbackClass: 'is-dragging',
fallbackOnBody: true,
@@ -30,7 +31,7 @@
scrollSpeed: 20,
onStart: gl.issueBoards.onStart,
onEnd: gl.issueBoards.onEnd
- }
+ };
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
return defaultSortOptions;
diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6
index cd942c8332b..31531c3ee34 100644
--- a/app/assets/javascripts/boards/models/issue.js.es6
+++ b/app/assets/javascripts/boards/models/issue.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-unused-vars, space-before-function-paren, arrow-body-style, space-in-parens, arrow-parens, comma-dangle, max-len */
+/* eslint-disable no-unused-vars, space-before-function-paren, arrow-body-style, arrow-parens, comma-dangle, max-len */
/* global Vue */
/* global ListLabel */
/* global ListMilestone */
@@ -37,12 +37,12 @@ class ListIssue {
}
findLabel (findLabel) {
- return this.labels.filter( label => label.title === findLabel.title )[0];
+ return this.labels.filter(label => label.title === findLabel.title)[0];
}
removeLabel (removeLabel) {
if (removeLabel) {
- this.labels = this.labels.filter( label => removeLabel.title !== label.title );
+ this.labels = this.labels.filter(label => removeLabel.title !== label.title);
}
}
@@ -51,7 +51,7 @@ class ListIssue {
}
getLists () {
- return gl.issueBoards.BoardsStore.state.lists.filter( list => list.findIssue(this.id) );
+ return gl.issueBoards.BoardsStore.state.lists.filter(list => list.findIssue(this.id));
}
update (url) {
@@ -60,7 +60,7 @@ class ListIssue {
milestone_id: this.milestone ? this.milestone.id : null,
due_date: this.dueDate,
assignee_id: this.assignee ? this.assignee.id : null,
- label_ids: this.labels.map( (label) => label.id )
+ label_ids: this.labels.map((label) => label.id)
}
};
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index fb13f529b11..3dd5f273057 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-plusplus, prefer-const, space-in-parens, no-shadow, no-param-reassign, max-len, no-unused-vars */
+/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */
/* global ListIssue */
/* global ListLabel */
@@ -58,7 +58,7 @@ class List {
nextPage () {
if (this.issuesSize > this.issues.length) {
- this.page++;
+ this.page += 1;
return this.getIssues(false);
}
@@ -66,12 +66,12 @@ class List {
getIssues (emptyIssues = true) {
const filters = this.filters;
- let data = { page: this.page };
+ const data = { page: this.page };
Object.keys(filters).forEach((key) => { data[key] = filters[key]; });
if (this.label) {
- data.label_name = data.label_name.filter( label => label !== this.label.title );
+ data.label_name = data.label_name.filter(label => label !== this.label.title);
}
if (emptyIssues) {
@@ -94,7 +94,7 @@ class List {
newIssue (issue) {
this.addIssue(issue);
- this.issuesSize++;
+ this.issuesSize += 1;
return gl.boardService.newIssue(this.id, issue)
.then((resp) => {
@@ -122,7 +122,7 @@ class List {
}
if (listFrom) {
- this.issuesSize++;
+ this.issuesSize += 1;
gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
.then(() => {
listFrom.getIssues(false);
@@ -132,7 +132,7 @@ class List {
}
findIssue (id) {
- return this.issues.filter( issue => issue.id === id )[0];
+ return this.issues.filter(issue => issue.id === id)[0];
}
removeIssue (removeIssue) {
@@ -140,7 +140,7 @@ class List {
const matchesRemove = removeIssue.id === issue.id;
if (matchesRemove) {
- this.issuesSize--;
+ this.issuesSize -= 1;
issue.removeLabel(this.label);
}
diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6
index f18633f0913..ea55158306b 100644
--- a/app/assets/javascripts/boards/services/board_service.js.es6
+++ b/app/assets/javascripts/boards/services/board_service.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable space-before-function-paren, comma-dangle, no-param-reassign, camelcase, prefer-const, no-extra-semi, max-len, no-unused-vars */
+/* eslint-disable space-before-function-paren, comma-dangle, no-param-reassign, camelcase, max-len, no-unused-vars */
/* global Vue */
class BoardService {
@@ -47,7 +47,7 @@ class BoardService {
}
getIssuesForList (id, filter = {}) {
- let data = { id };
+ const data = { id };
Object.keys(filter).forEach((key) => { data[key] = filter[key]; });
return this.issues.get(data);
diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6
index e7a14ea5bca..cdf1b09c0a4 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js.es6
+++ b/app/assets/javascripts/boards/stores/boards_store.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, space-before-function-paren, one-var, indent, space-in-parens, no-shadow, radix, dot-notation, semi, max-len */
+/* eslint-disable comma-dangle, space-before-function-paren, one-var, no-shadow, dot-notation, max-len */
/* global Cookies */
/* global List */
@@ -33,8 +33,8 @@
return list;
},
new (listObj) {
- const list = this.addList(listObj),
- backlogList = this.findList('type', 'backlog', 'backlog');
+ const list = this.addList(listObj);
+ const backlogList = this.findList('type', 'backlog', 'backlog');
list
.save()
@@ -52,7 +52,7 @@
},
shouldAddBlankState () {
// Decide whether to add the blank state
- return !(this.state.lists.filter( list => list.type !== 'backlog' && list.type !== 'done' )[0]);
+ return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'done')[0]);
},
addBlankState () {
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
@@ -82,20 +82,20 @@
if (!list) return;
- this.state.lists = this.state.lists.filter( list => list.id !== id );
+ this.state.lists = this.state.lists.filter(list => list.id !== id);
},
moveList (listFrom, orderLists) {
orderLists.forEach((id, i) => {
- const list = this.findList('id', parseInt(id));
+ const list = this.findList('id', parseInt(id, 10));
list.position = i;
});
listFrom.update();
},
moveIssueToList (listFrom, listTo, issue, newIndex) {
- const issueTo = listTo.findIssue(issue.id),
- issueLists = issue.getLists(),
- listLabels = issueLists.map( listIssue => listIssue.label );
+ const issueTo = listTo.findIssue(issue.id);
+ const issueLists = issue.getLists();
+ const listLabels = issueLists.map(listIssue => listIssue.label);
// Add to new lists issues if it doesn't already exist
if (!issueTo) {
@@ -105,7 +105,7 @@
if (listTo.type === 'done' && listFrom.type !== 'backlog') {
issueLists.forEach((list) => {
list.removeIssue(issue);
- })
+ });
issue.removeLabels(listLabels);
} else {
listFrom.removeIssue(issue);
diff --git a/app/assets/javascripts/boards/test_utils/simulate_drag.js b/app/assets/javascripts/boards/test_utils/simulate_drag.js
index 01e09ec482e..f05780167bf 100644
--- a/app/assets/javascripts/boards/test_utils/simulate_drag.js
+++ b/app/assets/javascripts/boards/test_utils/simulate_drag.js
@@ -1,120 +1,119 @@
-/* eslint-disable wrap-iife, func-names, strict, indent, no-tabs, no-var, vars-on-top, no-param-reassign, object-shorthand, no-shadow, comma-dangle, prefer-template, consistent-return, no-mixed-operators, no-unused-vars, object-curly-spacing, no-unused-expressions, prefer-arrow-callback, max-len */
+/* eslint-disable wrap-iife, func-names, strict, no-var, vars-on-top, no-param-reassign, object-shorthand, no-shadow, comma-dangle, prefer-template, consistent-return, no-mixed-operators, no-unused-vars, no-unused-expressions, prefer-arrow-callback, max-len */
(function () {
- 'use strict';
-
- function simulateEvent(el, type, options) {
- var event;
- if (!el) return;
- var ownerDocument = el.ownerDocument;
-
- options = options || {};
-
- if (/^mouse/.test(type)) {
- event = ownerDocument.createEvent('MouseEvents');
- event.initMouseEvent(type, true, true, 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 = ownerDocument.createEvent('CustomEvent');
-
- event.initCustomEvent(type, true, true, 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: {},
-
- setData: function (type, val) {
- this.data[type] = val;
- },
-
- getData: function (type) {
- return this.data[type];
- }
- };
- }
-
- if (el.dispatchEvent) {
- el.dispatchEvent(event);
- } else if (el.fireEvent) {
- el.fireEvent('on' + type, event);
- }
-
- return event;
- }
-
- function getTraget(target) {
- var el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
- var children = el.children;
-
- return (
- children[target.index] ||
- children[target.index === 'first' ? 0 : -1] ||
- children[target.index === 'last' ? children.length - 1 : -1]
- );
- }
-
- function getRect(el) {
- var rect = el.getBoundingClientRect();
- var width = rect.right - rect.left;
- var height = rect.bottom - rect.top;
-
- return {
- x: rect.left,
- y: rect.top,
- cx: rect.left + width / 2,
- cy: rect.top + height / 2,
- w: width,
- h: height,
- hw: width / 2,
- wh: height / 2
- };
- }
-
- function simulateDrag(options, callback) {
- options.to.el = options.to.el || options.from.el;
-
- var fromEl = getTraget(options.from);
- var toEl = getTraget(options.to);
+ 'use strict';
+
+ function simulateEvent(el, type, options) {
+ var event;
+ if (!el) return;
+ var ownerDocument = el.ownerDocument;
+
+ options = options || {};
+
+ if (/^mouse/.test(type)) {
+ event = ownerDocument.createEvent('MouseEvents');
+ event.initMouseEvent(type, true, true, 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 = ownerDocument.createEvent('CustomEvent');
+
+ event.initCustomEvent(type, true, true, 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: {},
+
+ setData: function (type, val) {
+ this.data[type] = val;
+ },
+
+ getData: function (type) {
+ return this.data[type];
+ }
+ };
+ }
+
+ if (el.dispatchEvent) {
+ el.dispatchEvent(event);
+ } else if (el.fireEvent) {
+ el.fireEvent('on' + type, event);
+ }
+
+ return event;
+ }
+
+ function getTraget(target) {
+ var el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
+ var children = el.children;
+
+ return (
+ children[target.index] ||
+ children[target.index === 'first' ? 0 : -1] ||
+ children[target.index === 'last' ? children.length - 1 : -1]
+ );
+ }
+
+ function getRect(el) {
+ var rect = el.getBoundingClientRect();
+ var width = rect.right - rect.left;
+ var height = rect.bottom - rect.top;
+
+ return {
+ x: rect.left,
+ y: rect.top,
+ cx: rect.left + width / 2,
+ cy: rect.top + height / 2,
+ w: width,
+ h: height,
+ hw: width / 2,
+ wh: height / 2
+ };
+ }
+
+ function simulateDrag(options, callback) {
+ options.to.el = options.to.el || options.from.el;
+
+ var fromEl = getTraget(options.from);
+ var toEl = getTraget(options.to);
var scrollable = options.scrollable;
- var fromRect = getRect(fromEl);
- var toRect = getRect(toEl);
-
- var startTime = new Date().getTime();
- var duration = options.duration || 1000;
- simulateEvent(fromEl, 'mousedown', {button: 0});
- options.ontap && options.ontap();
- window.SIMULATE_DRAG_ACTIVE = 1;
-
- var dragInterval = setInterval(function loop() {
- var progress = (new Date().getTime() - startTime) / duration;
- var x = (fromRect.cx + (toRect.cx - fromRect.cx) * progress) - scrollable.scrollLeft;
- var y = (fromRect.cy + (toRect.cy - fromRect.cy) * progress) - scrollable.scrollTop;
- var overEl = fromEl.ownerDocument.elementFromPoint(x, y);
-
- simulateEvent(overEl, 'mousemove', {
- clientX: x,
- clientY: y
- });
-
- if (progress >= 1) {
- options.ondragend && options.ondragend();
- simulateEvent(toEl, 'mouseup');
- clearInterval(dragInterval);
- window.SIMULATE_DRAG_ACTIVE = 0;
- }
- }, 100);
-
- return {
- target: fromEl,
- fromList: fromEl.parentNode,
- toList: toEl.parentNode
- };
- }
-
-
- // Export
- window.simulateEvent = simulateEvent;
- window.simulateDrag = simulateDrag;
+ var fromRect = getRect(fromEl);
+ var toRect = getRect(toEl);
+
+ var startTime = new Date().getTime();
+ var duration = options.duration || 1000;
+ simulateEvent(fromEl, 'mousedown', { button: 0 });
+ options.ontap && options.ontap();
+ window.SIMULATE_DRAG_ACTIVE = 1;
+
+ var dragInterval = setInterval(function loop() {
+ var progress = (new Date().getTime() - startTime) / duration;
+ var x = (fromRect.cx + (toRect.cx - fromRect.cx) * progress) - scrollable.scrollLeft;
+ var y = (fromRect.cy + (toRect.cy - fromRect.cy) * progress) - scrollable.scrollTop;
+ var overEl = fromEl.ownerDocument.elementFromPoint(x, y);
+
+ simulateEvent(overEl, 'mousemove', {
+ clientX: x,
+ clientY: y
+ });
+
+ if (progress >= 1) {
+ options.ondragend && options.ondragend();
+ simulateEvent(toEl, 'mouseup');
+ clearInterval(dragInterval);
+ window.SIMULATE_DRAG_ACTIVE = 0;
+ }
+ }, 100);
+
+ return {
+ target: fromEl,
+ fromList: fromEl.parentNode,
+ toList: toEl.parentNode
+ };
+ }
+
+ // Export
+ window.simulateEvent = simulateEvent;
+ window.simulateDrag = simulateDrag;
})();
diff --git a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6 b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6
index 3723a2039f9..54c2b4ad369 100644
--- a/app/assets/javascripts/boards/vue_resource_interceptor.js.es6
+++ b/app/assets/javascripts/boards/vue_resource_interceptor.js.es6
@@ -1,10 +1,10 @@
-/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars, no-plusplus */
+/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */
/* global Vue */
Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next(function (response) {
- Vue.activeResources--;
+ Vue.activeResources -= 1;
});
});
diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js
index a7e72430141..eae062a3aa3 100644
--- a/app/assets/javascripts/breakpoints.js
+++ b/app/assets/javascripts/breakpoints.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, padded-blocks, no-return-assign, new-parens, no-param-reassign, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */
(function() {
var Breakpoints = (function() {
@@ -52,7 +52,6 @@
};
return BreakpointInstance;
-
})();
Breakpoints.get = function() {
@@ -60,7 +59,6 @@
};
return Breakpoints;
-
})();
$((function(_this) {
diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js
index 30432dae278..dbdadc73c3f 100644
--- a/app/assets/javascripts/broadcast_message.js
+++ b/app/assets/javascripts/broadcast_message.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, max-len */
(function() {
$(function() {
var previewPath;
@@ -31,5 +31,4 @@
}
});
});
-
}).call(this);
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index bc13c46443a..0df84234520 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -1,10 +1,11 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, semi, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top */
/* global Breakpoints */
/* global Turbolinks */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
var AUTO_SCROLL_OFFSET = 75;
+ var DOWN_BUILD_TRACE = '#down-build-trace';
this.Build = (function() {
Build.interval = null;
@@ -26,7 +27,7 @@
this.$autoScrollStatus = $('#autoscroll-status');
this.$autoScrollStatusText = this.$autoScrollStatus.find('.status-text');
this.$upBuildTrace = $('#up-build-trace');
- this.$downBuildTrace = $('#down-build-trace');
+ this.$downBuildTrace = $(DOWN_BUILD_TRACE);
this.$scrollTopBtn = $('#scroll-top');
this.$scrollBottomBtn = $('#scroll-bottom');
this.$buildRefreshAnimation = $('.js-build-refresh');
@@ -69,7 +70,7 @@
this.$sidebar = $('.js-build-sidebar');
this.sidebarTranslationLimits = {
min: $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
- }
+ };
this.sidebarTranslationLimits.max = this.sidebarTranslationLimits.min + $('.scrolling-tabs-container').outerHeight();
this.$sidebar.css({
top: this.sidebarTranslationLimits.max
@@ -84,13 +85,16 @@
};
Build.prototype.getInitialBuildTrace = function() {
- var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped']
+ var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped'];
return $.ajax({
url: this.buildUrl,
dataType: 'json',
success: function(buildData) {
$('.js-build-output').html(buildData.trace_html);
+ if (window.location.hash === DOWN_BUILD_TRACE) {
+ $("html,body").scrollTop(this.$buildTrace.height());
+ }
if (removeRefreshStatuses.indexOf(buildData.status) >= 0) {
this.$buildRefreshAnimation.remove();
return this.initScrollMonitor();
@@ -105,6 +109,8 @@
dataType: "json",
success: (function(_this) {
return function(log) {
+ var pageUrl;
+
if (log.state) {
_this.state = log.state;
}
@@ -116,7 +122,12 @@
}
return _this.checkAutoscroll();
} else if (log.status !== _this.buildStatus) {
- return Turbolinks.visit(_this.pageUrl);
+ pageUrl = _this.pageUrl;
+ if (_this.$autoScrollStatus.data('state') === 'enabled') {
+ pageUrl += DOWN_BUILD_TRACE;
+ }
+
+ return Turbolinks.visit(pageUrl);
}
};
})(this)
@@ -142,7 +153,7 @@
this.$scrollTopBtn.hide();
this.$scrollBottomBtn.hide();
this.$autoScrollContainer.hide();
- }
+ };
// Page scroll listener to detect if user has scrolling page
// and handle following cases
@@ -280,7 +291,5 @@
};
return Build;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js
index c423a548a30..083448552b6 100644
--- a/app/assets/javascripts/build_artifacts.js
+++ b/app/assets/javascripts/build_artifacts.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, max-len */
(function() {
this.BuildArtifacts = (function() {
function BuildArtifacts() {
@@ -22,7 +22,5 @@
};
return BuildArtifacts;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/build_variables.js.es6 b/app/assets/javascripts/build_variables.js.es6
index 993424d422f..99082b412e2 100644
--- a/app/assets/javascripts/build_variables.js.es6
+++ b/app/assets/javascripts/build_variables.js.es6
@@ -1,7 +1,7 @@
-/* eslint-disable func-names, prefer-arrow-callback, space-before-blocks, space-before-function-paren, comma-spacing, max-len */
+/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren */
-$(function(){
- $('.reveal-variables').off('click').on('click',function(){
+$(function() {
+ $('.reveal-variables').off('click').on('click', function() {
$('.js-build').toggle().niceScroll();
$(this).hide();
});
diff --git a/app/assets/javascripts/ci_lint_editor.js.es6 b/app/assets/javascripts/ci_lint_editor.js.es6
new file mode 100644
index 00000000000..56ffaa765a8
--- /dev/null
+++ b/app/assets/javascripts/ci_lint_editor.js.es6
@@ -0,0 +1,18 @@
+(() => {
+ window.gl = window.gl || {};
+
+ class CILintEditor {
+ constructor() {
+ this.editor = window.ace.edit('ci-editor');
+ this.textarea = document.querySelector('#content');
+
+ this.editor.getSession().setMode('ace/mode/yaml');
+ this.editor.on('input', () => {
+ const content = this.editor.getSession().getValue();
+ this.textarea.value = content;
+ });
+ }
+ }
+
+ gl.CILintEditor = CILintEditor;
+})();
diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js
index 67b33a4d7ee..c656ae4e241 100644
--- a/app/assets/javascripts/commit.js
+++ b/app/assets/javascripts/commit.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife */
/* global CommitFile */
(function() {
@@ -10,7 +10,5 @@
}
return Commit;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js
index 27512312c7c..184b4561d2e 100644
--- a/app/assets/javascripts/commit/file.js
+++ b/app/assets/javascripts/commit/file.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new */
/* global ImageFile */
(function() {
@@ -10,7 +10,5 @@
}
return CommitFile;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js
index fd8910e916f..f09a6b1e676 100644
--- a/app/assets/javascripts/commit/image_file.js
+++ b/app/assets/javascripts/commit/image_file.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, max-len */
(function() {
gl.ImageFile = (function() {
var prepareFrames;
@@ -172,7 +172,5 @@
};
return ImageFile;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 24a6e4ff0e9..c6fdfbcaa10 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -1,9 +1,9 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len, prefer-arrow-callback */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, max-len, prefer-arrow-callback */
/* global Pager */
(function() {
this.CommitsList = (function() {
- function CommitsList() {}
+ var CommitsList = {};
CommitsList.timer = null;
@@ -20,6 +20,7 @@
});
this.content = $("#commits-list");
this.searchField = $("#commits-search");
+ this.lastSearch = this.searchField.val();
return this.initSearch();
};
@@ -37,6 +38,7 @@
var commitsUrl, form, search;
form = $(".commits-search-form");
search = CommitsList.searchField.val();
+ if (search === CommitsList.lastSearch) return;
commitsUrl = form.attr("action") + '?' + form.serialize();
CommitsList.content.fadeTo('fast', 0.5);
return $.ajax({
@@ -47,18 +49,20 @@
return CommitsList.content.fadeTo('fast', 1.0);
},
success: function(data) {
+ CommitsList.lastSearch = search;
CommitsList.content.html(data.html);
return history.replaceState({
page: commitsUrl
// Change url so if user reload a page - search results are saved
}, document.title, commitsUrl);
},
+ error: function() {
+ CommitsList.lastSearch = null;
+ },
dataType: "json"
});
};
return CommitsList;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js
index d4243baadb5..9591df70e9c 100644
--- a/app/assets/javascripts/compare.js
+++ b/app/assets/javascripts/compare.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
(function() {
this.Compare = (function() {
function Compare(opts) {
@@ -87,7 +87,5 @@
};
return Compare;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/compare_autocomplete.js.es6 b/app/assets/javascripts/compare_autocomplete.js.es6
index 45c974b2b68..3587431ab69 100644
--- a/app/assets/javascripts/compare_autocomplete.js.es6
+++ b/app/assets/javascripts/compare_autocomplete.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, no-dupe-keys, wrap-iife, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
(function() {
this.CompareAutocomplete = (function() {
@@ -28,7 +28,6 @@
selectable: true,
filterable: true,
filterByText: true,
- toggleLabel: true,
fieldName: $dropdown.data('field-name'),
filterInput: 'input[type="search"]',
renderRow: function(ref) {
@@ -55,11 +54,16 @@
$('.dropdown-toggle-text', $dropdown).text(text);
$dropdownContainer.removeClass('open');
});
+
+ $dropdownContainer.on('click', '.dropdown-content a', (e) => {
+ $dropdown.prop('title', e.target.text.replace(/_+?/g, '-'));
+ if ($dropdown.hasClass('has-tooltip')) {
+ $dropdown.tooltip('fixTitle');
+ }
+ });
});
};
return CompareAutocomplete;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
index 686a48486f3..35d98492012 100644
--- a/app/assets/javascripts/confirm_danger_modal.js
+++ b/app/assets/javascripts/confirm_danger_modal.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */
(function() {
this.ConfirmDangerModal = (function() {
function ConfirmDangerModal(form, text) {
@@ -27,7 +27,5 @@
}
return ConfirmDangerModal;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/copy_as_gfm.js.es6 b/app/assets/javascripts/copy_as_gfm.js.es6
new file mode 100644
index 00000000000..b94125a4210
--- /dev/null
+++ b/app/assets/javascripts/copy_as_gfm.js.es6
@@ -0,0 +1,355 @@
+/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
+/* jshint esversion: 6 */
+
+/*= require lib/utils/common_utils */
+
+(() => {
+ const gfmRules = {
+ // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
+ // GitLab Flavored Markdown (GFM) to HTML.
+ // These handlers consequently convert that same HTML to GFM to be copied to the clipboard.
+ // Every filter in lib/banzai/pipeline/gfm_pipeline.rb that generates HTML
+ // from GFM should have a handler here, in reverse order.
+ // The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb.
+ InlineDiffFilter: {
+ 'span.idiff.addition'(el, text) {
+ return `{+${text}+}`;
+ },
+ 'span.idiff.deletion'(el, text) {
+ return `{-${text}-}`;
+ },
+ },
+ TaskListFilter: {
+ 'input[type=checkbox].task-list-item-checkbox'(el, text) {
+ return `[${el.checked ? 'x' : ' '}]`;
+ },
+ },
+ ReferenceFilter: {
+ 'a.gfm:not([data-link=true])'(el, text) {
+ return el.dataset.original || text;
+ },
+ },
+ AutolinkFilter: {
+ 'a'(el, text) {
+ // Fallback on the regular MarkdownFilter's `a` handler.
+ if (text !== el.getAttribute('href')) return false;
+
+ return text;
+ },
+ },
+ TableOfContentsFilter: {
+ 'ul.section-nav'(el, text) {
+ return '[[_TOC_]]';
+ },
+ },
+ EmojiFilter: {
+ 'img.emoji'(el, text) {
+ return el.getAttribute('alt');
+ },
+ },
+ ImageLinkFilter: {
+ 'a.no-attachment-icon'(el, text) {
+ return text;
+ },
+ },
+ VideoLinkFilter: {
+ '.video-container'(el, text) {
+ const videoEl = el.querySelector('video');
+ if (!videoEl) return false;
+
+ return CopyAsGFM.nodeToGFM(videoEl);
+ },
+ 'video'(el, text) {
+ return `![${el.dataset.title}](${el.getAttribute('src')})`;
+ },
+ },
+ MathFilter: {
+ 'pre.code.math[data-math-style=display]'(el, text) {
+ return `\`\`\`math\n${text.trim()}\n\`\`\``;
+ },
+ 'code.code.math[data-math-style=inline]'(el, text) {
+ return `$\`${text}\`$`;
+ },
+ 'span.katex-display span.katex-mathml'(el, text) {
+ const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
+ if (!mathAnnotation) return false;
+
+ return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
+ },
+ 'span.katex-mathml'(el, text) {
+ const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
+ if (!mathAnnotation) return false;
+
+ return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
+ },
+ 'span.katex-html'(el, text) {
+ // We don't want to include the content of this element in the copied text.
+ return '';
+ },
+ 'annotation[encoding="application/x-tex"]'(el, text) {
+ return text.trim();
+ },
+ },
+ SanitizationFilter: {
+ 'dl'(el, text) {
+ let lines = text.trim().split('\n');
+ // Add two spaces to the front of subsequent list items lines,
+ // or leave the line entirely blank.
+ lines = lines.map((l) => {
+ const line = l.trim();
+ if (line.length === 0) return '';
+
+ return ` ${line}`;
+ });
+
+ return `<dl>\n${lines.join('\n')}\n</dl>`;
+ },
+ 'sub, dt, dd, kbd, q, samp, var, ruby, rt, rp, abbr'(el, text) {
+ const tag = el.nodeName.toLowerCase();
+ return `<${tag}>${text}</${tag}>`;
+ },
+ },
+ SyntaxHighlightFilter: {
+ 'pre.code.highlight'(el, t) {
+ const text = t.trim();
+
+ let lang = el.getAttribute('lang');
+ if (lang === 'plaintext') {
+ lang = '';
+ }
+
+ // 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 `\`\`\`${lang}\n${text}\n\`\`\``;
+ },
+ 'pre > code'(el, text) {
+ // Don't wrap code blocks in ``
+ return text;
+ },
+ },
+ MarkdownFilter: {
+ 'br'(el, text) {
+ // Two spaces at the end of a line are turned into a BR
+ return ' ';
+ },
+ 'code'(el, text) {
+ let backtickCount = 1;
+ const backtickMatch = text.match(/`+/);
+ if (backtickMatch) {
+ backtickCount = backtickMatch[0].length + 1;
+ }
+
+ const backticks = Array(backtickCount + 1).join('`');
+ const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
+
+ return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks;
+ },
+ 'blockquote'(el, text) {
+ return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
+ },
+ 'img'(el, text) {
+ return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
+ },
+ 'a.anchor'(el, text) {
+ // Don't render a Markdown link for the anchor link inside a heading
+ return text;
+ },
+ 'a'(el, text) {
+ return `[${text}](${el.getAttribute('href')})`;
+ },
+ '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) => {
+ if (s.trim().length === 0) return '';
+
+ return ` ${s}`;
+ });
+
+ return `${firstLine}\n${nextLines.join('\n')}`;
+ },
+ 'ul'(el, text) {
+ return text;
+ },
+ 'ol'(el, text) {
+ // LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists.
+ return text.replace(/^- /mg, '1. ');
+ },
+ 'h1'(el, text) {
+ return `# ${text.trim()}`;
+ },
+ 'h2'(el, text) {
+ return `## ${text.trim()}`;
+ },
+ 'h3'(el, text) {
+ return `### ${text.trim()}`;
+ },
+ 'h4'(el, text) {
+ return `#### ${text.trim()}`;
+ },
+ 'h5'(el, text) {
+ return `##### ${text.trim()}`;
+ },
+ 'h6'(el, text) {
+ return `###### ${text.trim()}`;
+ },
+ 'strong'(el, text) {
+ return `**${text}**`;
+ },
+ 'em'(el, text) {
+ return `_${text}_`;
+ },
+ 'del'(el, text) {
+ return `~~${text}~~`;
+ },
+ 'sup'(el, text) {
+ return `^${text}`;
+ },
+ 'hr'(el, text) {
+ return '-----';
+ },
+ 'table'(el, text) {
+ const theadEl = el.querySelector('thead');
+ const tbodyEl = el.querySelector('tbody');
+ if (!theadEl || !tbodyEl) return false;
+
+ const theadText = CopyAsGFM.nodeToGFM(theadEl);
+ const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
+
+ return theadText + tbodyText;
+ },
+ 'thead'(el, text) {
+ const cells = _.map(el.querySelectorAll('th'), (cell) => {
+ let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2;
+
+ let before = '';
+ let after = '';
+ switch (cell.style.textAlign) {
+ case 'center':
+ before = ':';
+ after = ':';
+ chars -= 2;
+ break;
+ case 'right':
+ after = ':';
+ chars -= 1;
+ break;
+ default:
+ break;
+ }
+
+ chars = Math.max(chars, 3);
+
+ const middle = Array(chars + 1).join('-');
+
+ return before + middle + after;
+ });
+
+ return `${text}|${cells.join('|')}|`;
+ },
+ 'tr'(el, text) {
+ const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim());
+ return `| ${cells.join(' | ')} |`;
+ },
+ },
+ };
+
+ class CopyAsGFM {
+ constructor() {
+ $(document).on('copy', '.md, .wiki', this.handleCopy);
+ $(document).on('paste', '.js-gfm-input', this.handlePaste);
+ }
+
+ handleCopy(e) {
+ const clipboardData = e.originalEvent.clipboardData;
+ if (!clipboardData) return;
+
+ const documentFragment = window.gl.utils.getSelectedFragment();
+ if (!documentFragment) return;
+
+ // If the documentFragment contains more than just Markdown, don't copy as GFM.
+ if (documentFragment.querySelector('.md, .wiki')) return;
+
+ e.preventDefault();
+ clipboardData.setData('text/plain', documentFragment.textContent);
+
+ const gfm = CopyAsGFM.nodeToGFM(documentFragment);
+ clipboardData.setData('text/x-gfm', gfm);
+ }
+
+ handlePaste(e) {
+ const clipboardData = e.originalEvent.clipboardData;
+ if (!clipboardData) return;
+
+ const gfm = clipboardData.getData('text/x-gfm');
+ if (!gfm) return;
+
+ e.preventDefault();
+
+ window.gl.utils.insertText(e.target, gfm);
+ }
+
+ static nodeToGFM(node) {
+ if (node.nodeType === Node.TEXT_NODE) {
+ return node.textContent;
+ }
+
+ const text = this.innerGFM(node);
+
+ if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+ return text;
+ }
+
+ for (const filter in gfmRules) {
+ const rules = gfmRules[filter];
+
+ for (const selector in rules) {
+ const func = rules[selector];
+
+ if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
+
+ const result = func(node, text);
+ if (result === false) continue;
+
+ return result;
+ }
+ }
+
+ return text;
+ }
+
+ static innerGFM(parentNode) {
+ const nodes = parentNode.childNodes;
+
+ const clonedParentNode = parentNode.cloneNode(true);
+ const clonedNodes = Array.prototype.slice.call(clonedParentNode.childNodes, 0);
+
+ for (let i = 0; i < nodes.length; i += 1) {
+ const node = nodes[i];
+ const clonedNode = clonedNodes[i];
+
+ const text = this.nodeToGFM(node);
+
+ // `clonedNode.replaceWith(text)` is not yet widely supported
+ clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
+ }
+
+ return clonedParentNode.innerText || clonedParentNode.textContent;
+ }
+ }
+
+ window.gl = window.gl || {};
+ window.gl.CopyAsGFM = CopyAsGFM;
+
+ new CopyAsGFM();
+})();
diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js
index 6a13f38588d..3485f8f91ed 100644
--- a/app/assets/javascripts/copy_to_clipboard.js
+++ b/app/assets/javascripts/copy_to_clipboard.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, max-len */
/* global Clipboard */
/*= require clipboard */
@@ -46,5 +46,4 @@
clipboard.on('success', genericSuccess);
return clipboard.on('error', genericError);
});
-
}).call(this);
diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6
index 9cf33e62958..35a029194d0 100644
--- a/app/assets/javascripts/diff.js.es6
+++ b/app/assets/javascripts/diff.js.es6
@@ -1,5 +1,7 @@
/* eslint-disable class-methods-use-this */
+//= require lib/utils/url_utility */
+
(() => {
const UNFOLD_COUNT = 20;
@@ -20,7 +22,7 @@
.on('click', '.js-unfold', this.handleClickUnfold.bind(this))
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
- this.highlighSelectedLine();
+ this.openAnchoredDiff();
}
handleClickUnfold(e) {
@@ -61,13 +63,22 @@
$.get(link, params, response => $target.parent().replaceWith(response));
}
- openAnchoredDiff(anchoredDiff, cb) {
- const diffTitle = $(`#file-path-${anchoredDiff}`);
+ openAnchoredDiff(cb) {
+ const locationHash = gl.utils.getLocationHash();
+ const anchoredDiff = locationHash && locationHash.split('_')[0];
+
+ if (!anchoredDiff) return;
+
+ const diffTitle = $(`#${anchoredDiff}`);
const diffFile = diffTitle.closest('.diff-file');
const nothingHereBlock = $('.nothing-here-block:visible', diffFile);
if (nothingHereBlock.length) {
- diffFile.singleFileDiff(true, cb);
- } else {
+ const clickTarget = $('.file-title, .click-to-expand', diffFile);
+ diffFile.data('singleFileDiff').toggleDiff(clickTarget, () => {
+ this.highlighSelectedLine();
+ if (cb) cb();
+ });
+ } else if (cb) {
cb();
}
}
@@ -95,11 +106,11 @@
}
highlighSelectedLine() {
+ const hash = gl.utils.getLocationHash();
const $diffFiles = $('.diff-file');
$diffFiles.find('.hll').removeClass('hll');
- if (window.location.hash !== '') {
- const hash = window.location.hash.replace('#', '');
+ if (hash) {
$diffFiles
.find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`)
.addClass('hll');
diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
index c59d3996fab..2514459e65e 100644
--- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, semi, max-len */
+/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */
/* global Vue */
/* global CommentsStore */
@@ -10,7 +10,7 @@
data() {
return {
textareaIsEmpty: true
- }
+ };
},
computed: {
discussion: function () {
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
index f47867fc3b0..c3898873eaa 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, indent, space-before-function-paren, no-plusplus, no-lonely-if, no-continue, brace-style, max-len, quotes, semi */
+/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, one-var, space-before-function-paren, no-lonely-if, no-continue, brace-style, max-len, quotes */
/* global Vue */
/* global DiscussionMixins */
/* global CommentsStore */
@@ -46,13 +46,13 @@
},
methods: {
jumpToNextUnresolvedDiscussion: function () {
- let discussionsSelector,
- discussionIdsInScope,
- firstUnresolvedDiscussionId,
- nextUnresolvedDiscussionId,
- activeTab = window.mrTabs.currentAction,
- hasDiscussionsToJumpTo = true,
- jumpToFirstDiscussion = !this.discussionId;
+ let discussionsSelector;
+ let discussionIdsInScope;
+ let firstUnresolvedDiscussionId;
+ let nextUnresolvedDiscussionId;
+ let activeTab = window.mrTabs.currentAction;
+ let hasDiscussionsToJumpTo = true;
+ let jumpToFirstDiscussion = !this.discussionId;
const discussionIdsForElements = function(elements) {
return elements.map(function() {
@@ -68,11 +68,11 @@
let unresolvedDiscussionCount = 0;
- for (let i = 0; i < discussionIdsInScope.length; i++) {
+ for (let i = 0; i < discussionIdsInScope.length; i += 1) {
const discussionId = discussionIdsInScope[i];
const discussion = discussions[discussionId];
if (discussion && !discussion.isResolved()) {
- unresolvedDiscussionCount++;
+ unresolvedDiscussionCount += 1;
}
}
@@ -109,7 +109,7 @@
}
let currentDiscussionFound = false;
- for (let i = 0; i < discussionIdsInScope.length; i++) {
+ for (let i = 0; i < discussionIdsInScope.length; i += 1) {
const discussionId = discussionIdsInScope[i];
const discussion = discussions[discussionId];
@@ -156,7 +156,7 @@
// If the next discussion is closed, toggle it open.
if ($target.find('.js-toggle-content').is(':hidden')) {
- $target.find('.js-toggle-button i').trigger('click')
+ $target.find('.js-toggle-button i').trigger('click');
}
} else if (activeTab === 'diffs') {
// Resolved discussions are hidden in the diffs tab by default.
@@ -170,7 +170,7 @@
// If we are on the diffs tab, we don't scroll to the discussion itself, but to
// 4 diff lines above it: the line the discussion was in response to + 3 context
let prevEl;
- for (let i = 0; i < 4; i++) {
+ for (let i = 0; i < 4; i += 1) {
prevEl = $target.prev();
// If the discussion doesn't have 4 lines above it, we'll have to do with fewer.
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
index 840b5aa922e..1b3a57d0962 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
@@ -2,8 +2,6 @@
/* global Vue */
/* global ResolveCount */
-//= require vue
-//= require vue-resource
//= require_directory ./models
//= require_directory ./stores
//= require_directory ./services
diff --git a/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 b/app/assets/javascripts/diff_notes/mixins/discussion.js.es6
index a9ea18bf82b..3c08c222f46 100644
--- a/app/assets/javascripts/diff_notes/mixins/discussion.js.es6
+++ b/app/assets/javascripts/diff_notes/mixins/discussion.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-plusplus, no-param-reassign, max-len */
+/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */
((w) => {
w.DiscussionMixins = {
@@ -13,7 +13,7 @@
const discussion = this.discussions[discussionId];
if (discussion.isResolved()) {
- resolvedCount++;
+ resolvedCount += 1;
}
}
@@ -26,7 +26,7 @@
const discussion = this.discussions[discussionId];
if (!discussion.isResolved()) {
- unresolvedCount++;
+ unresolvedCount += 1;
}
}
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js.es6 b/app/assets/javascripts/diff_notes/services/resolve.js.es6
index 78c74985f78..a52c476352d 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js.es6
+++ b/app/assets/javascripts/diff_notes/services/resolve.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable class-methods-use-this, one-var, indent, camelcase, no-new, comma-dangle, semi, no-param-reassign, max-len */
+/* eslint-disable class-methods-use-this, one-var, camelcase, no-new, comma-dangle, no-param-reassign, max-len */
/* global Vue */
/* global Flash */
/* global CommentsStore */
@@ -32,8 +32,8 @@
}
toggleResolveForDiscussion(projectPath, mergeRequestId, discussionId) {
- const discussion = CommentsStore.state[discussionId],
- isResolved = discussion.isResolved();
+ const discussion = CommentsStore.state[discussionId];
+ const isResolved = discussion.isResolved();
let promise;
if (isResolved) {
@@ -59,7 +59,7 @@
} else {
new Flash('An error occurred when trying to resolve a discussion. Please try again.', 'alert');
}
- })
+ });
}
resolveAll(projectPath, mergeRequestId, discussionId) {
diff --git a/app/assets/javascripts/diff_notes/stores/comments.js.es6 b/app/assets/javascripts/diff_notes/stores/comments.js.es6
index 1a7abbc6f75..c80d979b977 100644
--- a/app/assets/javascripts/diff_notes/stores/comments.js.es6
+++ b/app/assets/javascripts/diff_notes/stores/comments.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable object-shorthand, func-names, camelcase, prefer-const, no-restricted-syntax, guard-for-in, comma-dangle, max-len, no-param-reassign */
+/* eslint-disable object-shorthand, func-names, camelcase, no-restricted-syntax, guard-for-in, comma-dangle, max-len, no-param-reassign */
/* global Vue */
/* global DiscussionModel */
@@ -41,7 +41,7 @@
}
},
unresolvedDiscussionIds: function () {
- let ids = [];
+ const ids = [];
for (const discussionId in this.state) {
const discussion = this.state[discussionId];
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6
index 1c1b6cd2dad..529d476ca4e 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
/* global UsernameValidator */
/* global ActiveTabMemoizer */
/* global ShortcutsNavigation */
@@ -84,6 +84,9 @@
break;
case 'projects:merge_requests:index':
case 'projects:issues:index':
+ if (gl.FilteredSearchManager) {
+ new gl.FilteredSearchManager();
+ }
Issuable.init();
new gl.IssuableBulkActions({
prefixId: page === 'projects:merge_requests:index' ? 'merge_request_' : 'issue_',
@@ -184,11 +187,6 @@
new TreeView();
}
break;
- case 'projects:pipelines:index':
- new gl.MiniPipelineGraph({
- container: '.js-pipeline-table',
- });
- break;
case 'projects:pipelines:builds':
case 'projects:pipelines:show':
const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
@@ -215,7 +213,9 @@
new gl.Members();
new UsersSelect();
break;
- case 'projects:project_members:index':
+ case 'projects:members:show':
+ new gl.MemberExpirationDate('.js-access-expiration-date-groups');
+ new GroupsSelect();
new gl.MemberExpirationDate();
new gl.Members();
new UsersSelect();
@@ -261,9 +261,8 @@
case 'projects:artifacts:browse':
new BuildArtifacts();
break;
- case 'projects:group_links:index':
- new gl.MemberExpirationDate();
- new GroupsSelect();
+ case 'help:index':
+ gl.VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
break;
case 'search:show':
new Search();
@@ -275,6 +274,10 @@
case 'projects:variables:index':
new gl.ProjectVariables();
break;
+ case 'ci:lints:create':
+ case 'ci:lints:show':
+ new gl.CILintEditor();
+ break;
}
switch (path.first()) {
case 'admin':
@@ -372,7 +375,5 @@
};
return Dispatcher;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/droplab/droplab.js b/app/assets/javascripts/droplab/droplab.js
new file mode 100644
index 00000000000..8b14191395b
--- /dev/null
+++ b/app/assets/javascripts/droplab/droplab.js
@@ -0,0 +1,741 @@
+/* eslint-disable */
+// Determine where to place this
+if (typeof Object.assign != 'function') {
+ Object.assign = function (target, varArgs) { // .length of function is 2
+ 'use strict';
+ if (target == null) { // TypeError if undefined or null
+ throw new TypeError('Cannot convert undefined or null to object');
+ }
+
+ var to = Object(target);
+
+ for (var index = 1; index < arguments.length; index++) {
+ var nextSource = arguments[index];
+
+ if (nextSource != null) { // Skip over if undefined or null
+ for (var nextKey in nextSource) {
+ // Avoid bugs when hasOwnProperty is shadowed
+ if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ }
+ return to;
+ };
+}
+
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.droplab = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+var DATA_TRIGGER = 'data-dropdown-trigger';
+var DATA_DROPDOWN = 'data-dropdown';
+
+module.exports = {
+ DATA_TRIGGER: DATA_TRIGGER,
+ DATA_DROPDOWN: DATA_DROPDOWN,
+}
+
+},{}],2:[function(require,module,exports){
+// Custom event support for IE
+if ( typeof CustomEvent === "function" ) {
+ module.exports = CustomEvent;
+} else {
+ require('./window')(function(w){
+ var CustomEvent = function ( event, params ) {
+ params = params || { bubbles: false, cancelable: false, detail: undefined };
+ var evt = document.createEvent( 'CustomEvent' );
+ evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
+ return evt;
+ }
+ CustomEvent.prototype = w.Event.prototype;
+
+ w.CustomEvent = CustomEvent;
+ });
+ module.exports = CustomEvent;
+}
+
+},{"./window":11}],3:[function(require,module,exports){
+var CustomEvent = require('./custom_event_polyfill');
+var utils = require('./utils');
+
+var DropDown = function(list) {
+ this.currentIndex = 0;
+ this.hidden = true;
+ this.list = list;
+ this.items = [];
+ this.getItems();
+ this.initTemplateString();
+ this.addEvents();
+ this.initialState = list.innerHTML;
+};
+
+Object.assign(DropDown.prototype, {
+ getItems: function() {
+ this.items = [].slice.call(this.list.querySelectorAll('li'));
+ return this.items;
+ },
+
+ initTemplateString: function() {
+ var items = this.items || this.getItems();
+
+ var templateString = '';
+ if(items.length > 0) {
+ templateString = items[items.length - 1].outerHTML;
+ }
+ this.templateString = templateString;
+ return this.templateString;
+ },
+
+ clickEvent: function(e) {
+ // climb up the tree to find the LI
+ var selected = utils.closest(e.target, 'LI');
+
+ if(selected) {
+ e.preventDefault();
+ this.hide();
+ var listEvent = new CustomEvent('click.dl', {
+ detail: {
+ list: this,
+ selected: selected,
+ data: e.target.dataset,
+ },
+ });
+ this.list.dispatchEvent(listEvent);
+ }
+ },
+
+ addEvents: function() {
+ this.clickWrapper = this.clickEvent.bind(this);
+ // event delegation.
+ this.list.addEventListener('click', this.clickWrapper);
+ },
+
+ toggle: function() {
+ if(this.hidden) {
+ this.show();
+ } else {
+ this.hide();
+ }
+ },
+
+ setData: function(data) {
+ this.data = data;
+ this.render(data);
+ },
+
+ addData: function(data) {
+ this.data = (this.data || []).concat(data);
+ this.render(this.data);
+ },
+
+ // call render manually on data;
+ render: function(data){
+ // debugger
+ // empty the list first
+ var templateString = this.templateString;
+ var newChildren = [];
+ var toAppend;
+
+ newChildren = (data ||[]).map(function(dat){
+ var html = utils.t(templateString, dat);
+ var template = document.createElement('div');
+ template.innerHTML = html;
+
+ // Help set the image src template
+ var imageTags = template.querySelectorAll('img[data-src]');
+ // debugger
+ for(var i = 0; i < imageTags.length; i++) {
+ var imageTag = imageTags[i];
+ imageTag.src = imageTag.getAttribute('data-src');
+ imageTag.removeAttribute('data-src');
+ }
+
+ if(dat.hasOwnProperty('droplab_hidden') && dat.droplab_hidden){
+ template.firstChild.style.display = 'none'
+ }else{
+ template.firstChild.style.display = 'block';
+ }
+ return template.firstChild.outerHTML;
+ });
+ toAppend = this.list.querySelector('ul[data-dynamic]');
+ if(toAppend) {
+ toAppend.innerHTML = newChildren.join('');
+ } else {
+ this.list.innerHTML = newChildren.join('');
+ }
+ },
+
+ show: function() {
+ if (this.hidden) {
+ // debugger
+ this.list.style.display = 'block';
+ this.currentIndex = 0;
+ this.hidden = false;
+ }
+ },
+
+ hide: function() {
+ if (!this.hidden) {
+ // debugger
+ this.list.style.display = 'none';
+ this.currentIndex = 0;
+ this.hidden = true;
+ }
+ },
+
+ destroy: function() {
+ this.hide();
+ this.list.removeEventListener('click', this.clickWrapper);
+ }
+});
+
+module.exports = DropDown;
+
+},{"./custom_event_polyfill":2,"./utils":10}],4:[function(require,module,exports){
+require('./window')(function(w){
+ module.exports = function(deps) {
+ deps = deps || {};
+ var window = deps.window || w;
+ var document = deps.document || window.document;
+ var CustomEvent = deps.CustomEvent || require('./custom_event_polyfill');
+ var HookButton = deps.HookButton || require('./hook_button');
+ var HookInput = deps.HookInput || require('./hook_input');
+ var utils = deps.utils || require('./utils');
+ var DATA_TRIGGER = require('./constants').DATA_TRIGGER;
+
+ var DropLab = function(hook){
+ if (!(this instanceof DropLab)) return new DropLab(hook);
+ this.ready = false;
+ this.hooks = [];
+ this.queuedData = [];
+ this.config = {};
+ this.loadWrapper;
+ if(typeof hook !== 'undefined'){
+ this.addHook(hook);
+ }
+ };
+
+
+ Object.assign(DropLab.prototype, {
+ load: function() {
+ this.loadWrapper();
+ },
+
+ loadWrapper: function(){
+ var dropdownTriggers = [].slice.apply(document.querySelectorAll('['+DATA_TRIGGER+']'));
+ this.addHooks(dropdownTriggers).init();
+ },
+
+ addData: function () {
+ var args = [].slice.apply(arguments);
+ this.applyArgs(args, '_addData');
+ },
+
+ setData: function() {
+ var args = [].slice.apply(arguments);
+ this.applyArgs(args, '_setData');
+ },
+
+ destroy: function() {
+ for(var i = 0; i < this.hooks.length; i++) {
+ this.hooks[i].destroy();
+ }
+ this.hooks = [];
+ this.removeEvents();
+ },
+
+ applyArgs: function(args, methodName) {
+ if(this.ready) {
+ this[methodName].apply(this, args);
+ } else {
+ this.queuedData = this.queuedData || [];
+ this.queuedData.push(args);
+ }
+ },
+
+ _addData: function(trigger, data) {
+ this._processData(trigger, data, 'addData');
+ },
+
+ _setData: function(trigger, data) {
+ this._processData(trigger, data, 'setData');
+ },
+
+ _processData: function(trigger, data, methodName) {
+ for(var i = 0; i < this.hooks.length; i++) {
+ var hook = this.hooks[i];
+ if(hook.trigger.dataset.hasOwnProperty('id')) {
+ if(hook.trigger.dataset.id === trigger) {
+ hook.list[methodName](data);
+ }
+ }
+ }
+ },
+
+ addEvents: function() {
+ var self = this;
+ this.windowClickedWrapper = function(e){
+ var thisTag = e.target;
+ if(thisTag.tagName !== 'UL'){
+ // climb up the tree to find the UL
+ thisTag = utils.closest(thisTag, 'UL');
+ }
+ if(utils.isDropDownParts(thisTag)){ return }
+ if(utils.isDropDownParts(e.target)){ return }
+ for(var i = 0; i < self.hooks.length; i++) {
+ self.hooks[i].list.hide();
+ }
+ }.bind(this);
+ document.addEventListener('click', this.windowClickedWrapper);
+ },
+
+ removeEvents: function(){
+ w.removeEventListener('click', this.windowClickedWrapper);
+ w.removeEventListener('load', this.loadWrapper);
+ },
+
+ changeHookList: function(trigger, list, plugins, config) {
+ trigger = document.querySelector('[data-id="'+trigger+'"]');
+ // list = document.querySelector(list);
+ this.hooks.every(function(hook, i) {
+ if(hook.trigger === trigger) {
+ hook.destroy();
+ this.hooks.splice(i, 1);
+ this.addHook(trigger, list, plugins, config);
+ return false;
+ }
+ return true
+ }.bind(this));
+ },
+
+ addHook: function(hook, list, plugins, config) {
+ if(!(hook instanceof HTMLElement) && typeof hook === 'string'){
+ hook = document.querySelector(hook);
+ }
+ if(!list){
+ list = document.querySelector(hook.dataset[utils.toDataCamelCase(DATA_TRIGGER)]);
+ }
+
+ if(hook) {
+ if(hook.tagName === 'A' || hook.tagName === 'BUTTON') {
+ this.hooks.push(new HookButton(hook, list, plugins, config));
+ } else if(hook.tagName === 'INPUT') {
+ this.hooks.push(new HookInput(hook, list, plugins, config));
+ }
+ }
+ return this;
+ },
+
+ addHooks: function(hooks, plugins, config) {
+ for(var i = 0; i < hooks.length; i++) {
+ var hook = hooks[i];
+ this.addHook(hook, null, plugins, config);
+ }
+ return this;
+ },
+
+ setConfig: function(obj){
+ this.config = obj;
+ },
+
+ init: function () {
+ this.addEvents();
+ var readyEvent = new CustomEvent('ready.dl', {
+ detail: {
+ dropdown: this,
+ },
+ });
+ window.dispatchEvent(readyEvent);
+ this.ready = true;
+ for(var i = 0; i < this.queuedData.length; i++) {
+ this.addData.apply(this, this.queuedData[i]);
+ }
+ this.queuedData = [];
+ return this;
+ },
+ });
+
+ return DropLab;
+ };
+});
+
+},{"./constants":1,"./custom_event_polyfill":2,"./hook_button":6,"./hook_input":7,"./utils":10,"./window":11}],5:[function(require,module,exports){
+var DropDown = require('./dropdown');
+
+var Hook = function(trigger, list, plugins, config){
+ this.trigger = trigger;
+ this.list = new DropDown(list);
+ this.type = 'Hook';
+ this.event = 'click';
+ this.plugins = plugins || [];
+ this.config = config || {};
+ this.id = trigger.dataset.id;
+};
+
+Object.assign(Hook.prototype, {
+
+ addEvents: function(){},
+
+ constructor: Hook,
+});
+
+module.exports = Hook;
+
+},{"./dropdown":3}],6:[function(require,module,exports){
+var CustomEvent = require('./custom_event_polyfill');
+var Hook = require('./hook');
+
+var HookButton = function(trigger, list, plugins, config) {
+ Hook.call(this, trigger, list, plugins, config);
+ this.type = 'button';
+ this.event = 'click';
+ this.addEvents();
+ this.addPlugins();
+};
+
+HookButton.prototype = Object.create(Hook.prototype);
+
+Object.assign(HookButton.prototype, {
+ addPlugins: function() {
+ for(var i = 0; i < this.plugins.length; i++) {
+ this.plugins[i].init(this);
+ }
+ },
+
+ clicked: function(e){
+ var buttonEvent = new CustomEvent('click.dl', {
+ detail: {
+ hook: this,
+ },
+ bubbles: true,
+ cancelable: true
+ });
+ this.list.show();
+ e.target.dispatchEvent(buttonEvent);
+ },
+
+ addEvents: function(){
+ this.clickedWrapper = this.clicked.bind(this);
+ this.trigger.addEventListener('click', this.clickedWrapper);
+ },
+
+ removeEvents: function(){
+ this.trigger.removeEventListener('click', this.clickedWrapper);
+ },
+
+ restoreInitialState: function() {
+ this.list.list.innerHTML = this.list.initialState;
+ },
+
+ removePlugins: function() {
+ for(var i = 0; i < this.plugins.length; i++) {
+ this.plugins[i].destroy();
+ }
+ },
+
+ destroy: function() {
+ this.restoreInitialState();
+ this.removeEvents();
+ this.removePlugins();
+ },
+
+
+ constructor: HookButton,
+});
+
+
+module.exports = HookButton;
+
+},{"./custom_event_polyfill":2,"./hook":5}],7:[function(require,module,exports){
+var CustomEvent = require('./custom_event_polyfill');
+var Hook = require('./hook');
+
+var HookInput = function(trigger, list, plugins, config) {
+ Hook.call(this, trigger, list, plugins, config);
+ this.type = 'input';
+ this.event = 'input';
+ this.addPlugins();
+ this.addEvents();
+};
+
+Object.assign(HookInput.prototype, {
+ addPlugins: function() {
+ var self = this;
+ for(var i = 0; i < this.plugins.length; i++) {
+ this.plugins[i].init(self);
+ }
+ },
+
+ addEvents: function(){
+ var self = this;
+
+ this.mousedown = function mousedown(e) {
+ if(self.hasRemovedEvents) return;
+
+ var mouseEvent = new CustomEvent('mousedown.dl', {
+ detail: {
+ hook: self,
+ text: e.target.value,
+ },
+ bubbles: true,
+ cancelable: true
+ });
+ e.target.dispatchEvent(mouseEvent);
+ }
+
+ this.input = function input(e) {
+ if(self.hasRemovedEvents) return;
+
+ self.list.show();
+
+ var inputEvent = new CustomEvent('input.dl', {
+ detail: {
+ hook: self,
+ text: e.target.value,
+ },
+ bubbles: true,
+ cancelable: true
+ });
+ e.target.dispatchEvent(inputEvent);
+ }
+
+ this.keyup = function keyup(e) {
+ if(self.hasRemovedEvents) return;
+
+ keyEvent(e, 'keyup.dl');
+ }
+
+ this.keydown = function keydown(e) {
+ if(self.hasRemovedEvents) return;
+
+ keyEvent(e, 'keydown.dl');
+ }
+
+ function keyEvent(e, keyEventName){
+ self.list.show();
+
+ var keyEvent = new CustomEvent(keyEventName, {
+ detail: {
+ hook: self,
+ text: e.target.value,
+ which: e.which,
+ key: e.key,
+ },
+ bubbles: true,
+ cancelable: true
+ });
+ e.target.dispatchEvent(keyEvent);
+ }
+
+ this.events = this.events || {};
+ this.events.mousedown = this.mousedown;
+ this.events.input = this.input;
+ this.events.keyup = this.keyup;
+ this.events.keydown = this.keydown;
+ this.trigger.addEventListener('mousedown', this.mousedown);
+ this.trigger.addEventListener('input', this.input);
+ this.trigger.addEventListener('keyup', this.keyup);
+ this.trigger.addEventListener('keydown', this.keydown);
+ },
+
+ removeEvents: function() {
+ this.hasRemovedEvents = true;
+ this.trigger.removeEventListener('mousedown', this.mousedown);
+ this.trigger.removeEventListener('input', this.input);
+ this.trigger.removeEventListener('keyup', this.keyup);
+ this.trigger.removeEventListener('keydown', this.keydown);
+ },
+
+ restoreInitialState: function() {
+ this.list.list.innerHTML = this.list.initialState;
+ },
+
+ removePlugins: function() {
+ for(var i = 0; i < this.plugins.length; i++) {
+ this.plugins[i].destroy();
+ }
+ },
+
+ destroy: function() {
+ this.restoreInitialState();
+ this.removeEvents();
+ this.removePlugins();
+ this.list.destroy();
+ }
+});
+
+module.exports = HookInput;
+
+},{"./custom_event_polyfill":2,"./hook":5}],8:[function(require,module,exports){
+var DropLab = require('./droplab')();
+var DATA_TRIGGER = require('./constants').DATA_TRIGGER;
+var keyboard = require('./keyboard')();
+var setup = function() {
+ window.DropLab = DropLab;
+};
+
+
+module.exports = setup();
+
+},{"./constants":1,"./droplab":4,"./keyboard":9}],9:[function(require,module,exports){
+require('./window')(function(w){
+ module.exports = function(){
+ var currentKey;
+ var currentFocus;
+ var isUpArrow = false;
+ var isDownArrow = false;
+ var removeHighlight = function removeHighlight(list) {
+ var listItems = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0);
+ var listItemsTmp = [];
+ for(var i = 0; i < listItems.length; i++) {
+ var listItem = listItems[i];
+ listItem.classList.remove('dropdown-active');
+
+ if (listItem.style.display !== 'none') {
+ listItemsTmp.push(listItem);
+ }
+ }
+ return listItemsTmp;
+ };
+
+ var setMenuForArrows = function setMenuForArrows(list) {
+ var listItems = removeHighlight(list);
+ 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];
+ var filterDropdownEl = el.closest('.filter-dropdown');
+ el.classList.add('dropdown-active');
+
+ if (filterDropdownEl) {
+ var filterDropdownBottom = filterDropdownEl.offsetHeight;
+ var elOffsetTop = el.offsetTop - 30;
+
+ if (elOffsetTop > filterDropdownBottom) {
+ filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom;
+ }
+ }
+ }
+ }
+ };
+
+ var mousedown = function mousedown(e) {
+ var list = e.detail.hook.list;
+ removeHighlight(list);
+ list.show();
+ list.currentIndex = 0;
+ isUpArrow = false;
+ isDownArrow = false;
+ };
+ var selectItem = function selectItem(list) {
+ var listItems = removeHighlight(list);
+ var currentItem = listItems[list.currentIndex-1];
+ var listEvent = new CustomEvent('click.dl', {
+ detail: {
+ list: list,
+ selected: currentItem,
+ data: currentItem.dataset,
+ },
+ });
+ list.list.dispatchEvent(listEvent);
+ list.hide();
+ }
+
+ 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){
+ currentKey = e.detail.which;
+ if(currentKey === 13){
+ selectItem(e.detail.hook.list);
+ return;
+ }
+ if(currentKey === 38) {
+ isUpArrow = true;
+ }
+ if(currentKey === 40) {
+ isDownArrow = true;
+ }
+ } else if(e.detail.key) {
+ currentKey = e.detail.key;
+ if(currentKey === 'Enter'){
+ selectItem(e.detail.hook.list);
+ return;
+ }
+ if(currentKey === 'ArrowUp') {
+ isUpArrow = true;
+ }
+ if(currentKey === 'ArrowDown') {
+ isDownArrow = true;
+ }
+ }
+ if(isUpArrow){ currentIndex--; }
+ if(isDownArrow){ currentIndex++; }
+ if(currentIndex < 0){ currentIndex = 0; }
+ list.currentIndex = currentIndex;
+ setMenuForArrows(e.detail.hook.list);
+ };
+
+ w.addEventListener('mousedown.dl', mousedown);
+ w.addEventListener('keydown.dl', keydown);
+ };
+});
+},{"./window":11}],10:[function(require,module,exports){
+var DATA_TRIGGER = require('./constants').DATA_TRIGGER;
+var DATA_DROPDOWN = require('./constants').DATA_DROPDOWN;
+
+var toDataCamelCase = function(attr){
+ return this.camelize(attr.split('-').slice(1).join(' '));
+};
+
+// the tiniest damn templating I can do
+var t = function(s,d){
+ for(var p in d)
+ s=s.replace(new RegExp('{{'+p+'}}','g'), d[p]);
+ return s;
+};
+
+var camelize = function(str) {
+ return str.replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) {
+ return index == 0 ? letter.toLowerCase() : letter.toUpperCase();
+ }).replace(/\s+/g, '');
+};
+
+var closest = function(thisTag, stopTag) {
+ while(thisTag && thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML'){
+ thisTag = thisTag.parentNode;
+ }
+ return thisTag;
+};
+
+var isDropDownParts = function(target) {
+ if(!target || target.tagName === 'HTML') { return false; }
+ return (
+ target.hasAttribute(DATA_TRIGGER) ||
+ target.hasAttribute(DATA_DROPDOWN)
+ );
+};
+
+module.exports = {
+ toDataCamelCase: toDataCamelCase,
+ t: t,
+ camelize: camelize,
+ closest: closest,
+ isDropDownParts: isDropDownParts,
+};
+
+},{"./constants":1}],11:[function(require,module,exports){
+module.exports = function(callback) {
+ return (function() {
+ callback(this);
+ }).call(null);
+};
+
+},{}]},{},[8])(8)
+});
diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js
new file mode 100644
index 00000000000..c290e1a8355
--- /dev/null
+++ b/app/assets/javascripts/droplab/droplab_ajax.js
@@ -0,0 +1,96 @@
+/* eslint-disable */
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/* global droplab */
+
+require('../window')(function(w){
+ function droplabAjaxException(message) {
+ this.message = message;
+ }
+
+ w.droplabAjax = {
+ _loadUrlData: function _loadUrlData(url) {
+ var self = this;
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest;
+ xhr.open('GET', url, true);
+ xhr.onreadystatechange = function () {
+ if(xhr.readyState === XMLHttpRequest.DONE) {
+ if (xhr.status === 200) {
+ var data = JSON.parse(xhr.responseText);
+ self.cache[url] = data;
+ return resolve(data);
+ } else {
+ return reject([xhr.responseText, xhr.status]);
+ }
+ }
+ };
+ xhr.send();
+ });
+ },
+
+ _loadData: function _loadData(data, config, self) {
+ if (config.loadingTemplate) {
+ var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
+
+ if (dataLoadingTemplate) {
+ dataLoadingTemplate.outerHTML = self.listTemplate;
+ }
+ }
+
+ self.hook.list[config.method].call(self.hook.list, data);
+ },
+
+ init: function init(hook) {
+ var self = this;
+ self.cache = self.cache || {};
+ var config = hook.config.droplabAjax;
+ this.hook = hook;
+
+ if (!config || !config.endpoint || !config.method) {
+ return;
+ }
+
+ if (config.method !== 'setData' && config.method !== 'addData') {
+ return;
+ }
+
+ if (config.loadingTemplate) {
+ var dynamicList = hook.list.list.querySelector('[data-dynamic]');
+
+ var loadingTemplate = document.createElement('div');
+ loadingTemplate.innerHTML = config.loadingTemplate;
+ loadingTemplate.setAttribute('data-loading-template', '');
+
+ this.listTemplate = dynamicList.outerHTML;
+ dynamicList.outerHTML = loadingTemplate.outerHTML;
+ }
+
+ if (self.cache[config.endpoint]) {
+ self._loadData(self.cache[config.endpoint], config, self);
+ } else {
+ this._loadUrlData(config.endpoint)
+ .then(function(d) {
+ self._loadData(d, config, self);
+ }).catch(function(e) {
+ throw new droplabAjaxException(e.message || e);
+ });
+ }
+ },
+
+ destroy: function() {
+ if (this.listTemplate) {
+ var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
+ dynamicList.outerHTML = this.listTemplate;
+ }
+ }
+ };
+});
+},{"../window":2}],2:[function(require,module,exports){
+module.exports = function(callback) {
+ return (function() {
+ callback(this);
+ }).call(null);
+};
+
+},{}]},{},[1])(1)
+});
diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js
new file mode 100644
index 00000000000..b63d73066cb
--- /dev/null
+++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js
@@ -0,0 +1,161 @@
+/* eslint-disable */
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/* global droplab */
+
+require('../window')(function(w){
+ w.droplabAjaxFilter = {
+ init: function(hook) {
+ this.destroyed = false;
+ this.hook = hook;
+ this.notLoading();
+
+ this.debounceTriggerWrapper = this.debounceTrigger.bind(this);
+ this.hook.trigger.addEventListener('keydown.dl', this.debounceTriggerWrapper);
+ this.hook.trigger.addEventListener('focus', this.debounceTriggerWrapper);
+ this.trigger(true);
+ },
+
+ notLoading: function notLoading() {
+ this.loading = false;
+ },
+
+ debounceTrigger: function debounceTrigger(e) {
+ var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93];
+ var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1;
+ var focusEvent = e.type === 'focus';
+
+ if (invalidKeyPressed || this.loading) {
+ return;
+ }
+
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ }
+
+ this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200);
+ },
+
+ trigger: function trigger(getEntireList) {
+ var config = this.hook.config.droplabAjaxFilter;
+ var searchValue = this.trigger.value;
+
+ if (!config || !config.endpoint || !config.searchKey) {
+ return;
+ }
+
+ if (config.searchValueFunction) {
+ searchValue = config.searchValueFunction();
+ }
+
+ 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;
+ loadingTemplate.setAttribute('data-loading-template', true);
+
+ this.listTemplate = dynamicList.outerHTML;
+ dynamicList.outerHTML = loadingTemplate.outerHTML;
+ }
+
+ if (getEntireList) {
+ searchValue = '';
+ }
+
+ if (config.searchKey === searchValue) {
+ return this.list.show();
+ }
+
+ this.loading = true;
+
+ var params = config.params || {};
+ params[config.searchKey] = searchValue;
+ var self = this;
+ self.cache = self.cache || {};
+ var url = config.endpoint + this.buildParams(params);
+ var urlCachedData = self.cache[url];
+
+ if (urlCachedData) {
+ self._loadData(urlCachedData, config, self);
+ } else {
+ this._loadUrlData(url)
+ .then(function(data) {
+ self._loadData(data, config, self);
+ });
+ }
+ },
+
+ _loadUrlData: function _loadUrlData(url) {
+ var self = this;
+ return new Promise(function(resolve, reject) {
+ var xhr = new XMLHttpRequest;
+ xhr.open('GET', url, true);
+ xhr.onreadystatechange = function () {
+ if(xhr.readyState === XMLHttpRequest.DONE) {
+ if (xhr.status === 200) {
+ var data = JSON.parse(xhr.responseText);
+ self.cache[url] = data;
+ return resolve(data);
+ } else {
+ return reject([xhr.responseText, xhr.status]);
+ }
+ }
+ };
+ xhr.send();
+ });
+ },
+
+ _loadData: function _loadData(data, config, self) {
+ if (config.loadingTemplate && self.hook.list.data === undefined ||
+ self.hook.list.data.length === 0) {
+ const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
+
+ if (dataLoadingTemplate) {
+ dataLoadingTemplate.outerHTML = self.listTemplate;
+ }
+ }
+
+ if (!self.destroyed) {
+ var hookListChildren = self.hook.list.list.children;
+ var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
+
+ if (onlyDynamicList && data.length === 0) {
+ self.hook.list.hide();
+ }
+
+ self.hook.list.setData.call(self.hook.list, data);
+ }
+ self.notLoading();
+ self.hook.list.currentIndex = 0;
+ },
+
+ buildParams: function(params) {
+ if (!params) return '';
+ var paramsArray = Object.keys(params).map(function(param) {
+ return param + '=' + (params[param] || '');
+ });
+ return '?' + paramsArray.join('&');
+ },
+
+ destroy: function destroy() {
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ }
+
+ this.destroyed = true;
+
+ this.hook.trigger.removeEventListener('keydown.dl', this.debounceTriggerWrapper);
+ this.hook.trigger.removeEventListener('focus', this.debounceTriggerWrapper);
+ }
+ };
+});
+},{"../window":2}],2:[function(require,module,exports){
+module.exports = function(callback) {
+ return (function() {
+ callback(this);
+ }).call(null);
+};
+
+},{}]},{},[1])(1)
+});
diff --git a/app/assets/javascripts/droplab/droplab_filter.js b/app/assets/javascripts/droplab/droplab_filter.js
new file mode 100644
index 00000000000..9b40a3f20a4
--- /dev/null
+++ b/app/assets/javascripts/droplab/droplab_filter.js
@@ -0,0 +1,74 @@
+/* eslint-disable */
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.filter||(g.filter = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/* global droplab */
+
+require('../window')(function(w){
+ w.droplabFilter = {
+
+ keydownWrapper: function(e){
+ var hiddenCount = 0;
+ var dataHiddenCount = 0;
+ var list = e.detail.hook.list;
+ var data = list.data;
+ var value = e.detail.hook.trigger.value.toLowerCase();
+ var config = e.detail.hook.config.droplabFilter;
+ var matches = [];
+ var filterFunction;
+ // will only work on dynamically set data
+ if(!data){
+ return;
+ }
+
+ if (config && config.filterFunction && typeof config.filterFunction === 'function') {
+ filterFunction = config.filterFunction;
+ } else {
+ filterFunction = function(o){
+ // cheap string search
+ o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
+ return o;
+ };
+ }
+
+ dataHiddenCount = data.filter(function(o) {
+ return !o.droplab_hidden;
+ }).length;
+
+ matches = data.map(function(o) {
+ return filterFunction(o, value);
+ });
+
+ hiddenCount = matches.filter(function(o) {
+ return !o.droplab_hidden;
+ }).length;
+
+ if (dataHiddenCount !== hiddenCount) {
+ list.render(matches);
+ list.currentIndex = 0;
+ }
+ },
+
+ init: function init(hookInput) {
+ var config = hookInput.config.droplabFilter;
+
+ if (!config || (!config.template && !config.filterFunction)) {
+ return;
+ }
+
+ this.hookInput = hookInput;
+ this.hookInput.trigger.addEventListener('keyup.dl', this.keydownWrapper);
+ },
+
+ destroy: function destroy(){
+ this.hookInput.trigger.removeEventListener('keyup.dl', this.keydownWrapper);
+ }
+ };
+});
+},{"../window":2}],2:[function(require,module,exports){
+module.exports = function(callback) {
+ return (function() {
+ callback(this);
+ }).call(null);
+};
+
+},{}]},{},[1])(1)
+});
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 56cb39be642..3d183f4ecb4 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, no-plusplus, prefer-arrow-callback, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */
/* global Dropzone */
/*= require preview_markdown */
@@ -120,7 +120,7 @@
if (item.type.indexOf("image") !== -1) {
return item;
}
- i++;
+ i += 1;
}
return false;
};
@@ -215,7 +215,5 @@
}
return DropzoneInput;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js.es6
index 2b7d57d86c6..d81d4cf8425 100644
--- a/app/assets/javascripts/due_date_select.js.es6
+++ b/app/assets/javascripts/due_date_select.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, prefer-const, padded-blocks, no-unused-vars, no-underscore-dangle, no-new, max-len, semi, no-sequences, no-unused-expressions, no-param-reassign */
+/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */
(function(global) {
class DueDateSelect {
@@ -16,7 +16,7 @@
this.$sidebarValue = $('.js-due-date-sidebar-value', $block);
this.fieldName = $dropdown.data('field-name'),
this.abilityName = $dropdown.data('ability-name'),
- this.issueUpdateURL = $dropdown.data('issue-update')
+ this.issueUpdateURL = $dropdown.data('issue-update');
this.rawSelectedDate = null;
this.displayedDate = null;
@@ -80,9 +80,12 @@
}
parseSelectedDate() {
- this.rawSelectedDate = $("input[name='" + this.fieldName + "']").val();
+ this.rawSelectedDate = $(`input[name='${this.fieldName}']`).val();
+
if (this.rawSelectedDate.length) {
- let dateObj = new Date(this.rawSelectedDate);
+ // Construct Date object manually to avoid buggy dateString support within Date constructor
+ const dateArray = this.rawSelectedDate.split('-').map(v => parseInt(v, 10));
+ const dateObj = new Date(dateArray[0], dateArray[1] - 1, dateArray[2]);
this.displayedDate = $.datepicker.formatDate('M d, yy', dateObj);
} else {
this.displayedDate = 'No due date';
@@ -132,7 +135,6 @@
return selectedDateValue.length ?
$('.js-remove-due-date-holder').removeClass('hidden') :
$('.js-remove-due-date-holder').addClass('hidden');
-
}
}).done((data) => {
if (isDropdown) {
@@ -176,5 +178,4 @@
}
global.DueDateSelectors = DueDateSelectors;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6
index facd653fd72..fea642467fa 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js.es6
@@ -1,6 +1,7 @@
-/* eslint-disable no-param-reassign */
+/* eslint-disable no-param-reassign, no-new */
/* global Vue */
/* global EnvironmentsService */
+/* global Flash */
//= require vue
//= require vue-resource
@@ -10,42 +11,7 @@
(() => {
window.gl = window.gl || {};
- /**
- * Given the visibility prop provided by the url query parameter and which
- * changes according to the active tab we need to filter which environments
- * should be visible.
- *
- * The environments array is a recursive tree structure and we need to filter
- * both root level environments and children environments.
- *
- * In order to acomplish that, both `filterState` and `filterEnvironmentsByState`
- * functions work together.
- * The first one works as the filter that verifies if the given environment matches
- * the given state.
- * The second guarantees both root level and children elements are filtered as well.
- */
-
- const filterState = state => environment => environment.state === state && environment;
- /**
- * Given the filter function and the array of environments will return only
- * the environments that match the state provided to the filter function.
- *
- * @param {Function} fn
- * @param {Array} array
- * @return {Array}
- */
- const filterEnvironmentsByState = (fn, arr) => arr.map((item) => {
- if (item.children) {
- const filteredChildren = filterEnvironmentsByState(fn, item.children).filter(Boolean);
- if (filteredChildren.length) {
- item.children = filteredChildren;
- return item;
- }
- }
- return fn(item);
- }).filter(Boolean);
-
- window.gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', {
+ gl.environmentsList.EnvironmentsComponent = Vue.component('environment-component', {
props: {
store: {
type: Object,
@@ -55,7 +21,7 @@
},
components: {
- 'environment-item': window.gl.environmentsList.EnvironmentItem,
+ 'environment-item': gl.environmentsList.EnvironmentItem,
},
data() {
@@ -81,10 +47,6 @@
},
computed: {
- filteredEnvironments() {
- return filterEnvironmentsByState(filterState(this.visibility), this.state.environments);
- },
-
scope() {
return this.$options.getQueryParameter('scope');
},
@@ -111,7 +73,7 @@
const scope = this.$options.getQueryParameter('scope');
if (scope) {
- this.visibility = scope;
+ this.store.storeVisibility(scope);
}
this.isLoading = true;
@@ -121,6 +83,10 @@
.then((json) => {
this.store.storeEnvironments(json);
this.isLoading = false;
+ })
+ .catch(() => {
+ this.isLoading = false;
+ new Flash('An error occurred while fetching the environments.', 'alert');
});
},
@@ -188,7 +154,7 @@
<div class="blank-state blank-state-no-icon"
v-if="!isLoading && state.environments.length === 0">
- <h2 class="blank-state-title">
+ <h2 class="blank-state-title js-blank-state-title">
You don't have any environments right now.
</h2>
<p class="blank-state-text">
@@ -202,13 +168,13 @@
<a
v-if="canCreateEnvironmentParsed"
:href="newEnvironmentPath"
- class="btn btn-create">
+ class="btn btn-create js-new-environment-button">
New Environment
</a>
</div>
<div class="table-holder"
- v-if="!isLoading && state.environments.length > 0">
+ v-if="!isLoading && state.filteredEnvironments.length > 0">
<table class="table ci-table environments">
<thead>
<tr>
@@ -216,12 +182,12 @@
<th class="environments-deploy">Last deployment</th>
<th class="environments-build">Build</th>
<th class="environments-commit">Commit</th>
- <th class="environments-date"></th>
+ <th class="environments-date">Created</th>
<th class="hidden-xs environments-actions"></th>
</tr>
</thead>
<tbody>
- <template v-for="model in filteredEnvironments"
+ <template v-for="model in state.filteredEnvironments"
v-bind:model="model">
<tr
diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6
index 7c743705d51..81468f4d3bc 100644
--- a/app/assets/javascripts/environments/components/environment_actions.js.es6
+++ b/app/assets/javascripts/environments/components/environment_actions.js.es6
@@ -5,7 +5,7 @@
window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {};
- window.gl.environmentsList.ActionsComponent = Vue.component('actions-component', {
+ gl.environmentsList.ActionsComponent = Vue.component('actions-component', {
props: {
actions: {
type: Array,
diff --git a/app/assets/javascripts/environments/components/environment_external_url.js.es6 b/app/assets/javascripts/environments/components/environment_external_url.js.es6
index aed65b33c04..6592c1b5f0f 100644
--- a/app/assets/javascripts/environments/components/environment_external_url.js.es6
+++ b/app/assets/javascripts/environments/components/environment_external_url.js.es6
@@ -5,7 +5,7 @@
window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {};
- window.gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', {
+ gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', {
props: {
externalUrl: {
type: String,
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6
index b26a40aa268..0e6bc3fdb2c 100644
--- a/app/assets/javascripts/environments/components/environment_item.js.es6
+++ b/app/assets/javascripts/environments/components/environment_item.js.es6
@@ -29,12 +29,12 @@
gl.environmentsList.EnvironmentItem = Vue.component('environment-item', {
components: {
- 'commit-component': window.gl.CommitComponent,
- 'actions-component': window.gl.environmentsList.ActionsComponent,
- 'external-url-component': window.gl.environmentsList.ExternalUrlComponent,
- 'stop-component': window.gl.environmentsList.StopComponent,
- 'rollback-component': window.gl.environmentsList.RollbackComponent,
- 'terminal-button-component': window.gl.environmentsList.TerminalButtonComponent,
+ 'commit-component': gl.CommitComponent,
+ 'actions-component': gl.environmentsList.ActionsComponent,
+ 'external-url-component': gl.environmentsList.ExternalUrlComponent,
+ 'stop-component': gl.environmentsList.StopComponent,
+ 'rollback-component': gl.environmentsList.RollbackComponent,
+ 'terminal-button-component': gl.environmentsList.TerminalButtonComponent,
},
props: {
@@ -183,7 +183,7 @@
* @returns {String}
*/
createdDate() {
- return window.gl.environmentsList.timeagoInstance.format(
+ return gl.environmentsList.timeagoInstance.format(
this.model.last_deployment.deployable.created_at,
);
},
diff --git a/app/assets/javascripts/environments/components/environment_rollback.js.es6 b/app/assets/javascripts/environments/components/environment_rollback.js.es6
index 6d4e8fad604..b52298b4a88 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.js.es6
+++ b/app/assets/javascripts/environments/components/environment_rollback.js.es6
@@ -5,7 +5,7 @@
window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {};
- window.gl.environmentsList.RollbackComponent = Vue.component('rollback-component', {
+ gl.environmentsList.RollbackComponent = Vue.component('rollback-component', {
props: {
retryUrl: {
type: String,
diff --git a/app/assets/javascripts/environments/components/environment_stop.js.es6 b/app/assets/javascripts/environments/components/environment_stop.js.es6
index 7292f924e5c..0a29f2f36e9 100644
--- a/app/assets/javascripts/environments/components/environment_stop.js.es6
+++ b/app/assets/javascripts/environments/components/environment_stop.js.es6
@@ -5,7 +5,7 @@
window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {};
- window.gl.environmentsList.StopComponent = Vue.component('stop-component', {
+ gl.environmentsList.StopComponent = Vue.component('stop-component', {
props: {
stopUrl: {
type: String,
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6
index 25e6ac7f3c9..050184ba497 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.js.es6
@@ -5,7 +5,7 @@
window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {};
- window.gl.environmentsList.TerminalButtonComponent = Vue.component('terminal-button-component', {
+ gl.environmentsList.TerminalButtonComponent = Vue.component('terminal-button-component', {
props: {
terminalPath: {
type: String,
diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6
index 20eee7976ec..3b003f6f661 100644
--- a/app/assets/javascripts/environments/environments_bundle.js.es6
+++ b/app/assets/javascripts/environments/environments_bundle.js.es6
@@ -3,19 +3,20 @@
//= require ./components/environment
//= require ./vue_resource_interceptor
-
$(() => {
window.gl = window.gl || {};
- if (window.gl.EnvironmentsListApp) {
- window.gl.EnvironmentsListApp.$destroy(true);
+ if (gl.EnvironmentsListApp) {
+ gl.EnvironmentsListApp.$destroy(true);
}
- const Store = window.gl.environmentsList.EnvironmentsStore;
+ const Store = gl.environmentsList.EnvironmentsStore;
- window.gl.EnvironmentsListApp = new window.gl.environmentsList.EnvironmentsComponent({
+ gl.EnvironmentsListApp = new gl.environmentsList.EnvironmentsComponent({
el: document.querySelector('#environments-list-view'),
+
propsData: {
store: Store.create(),
},
+
});
});
diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js.es6
index 0204a903ab5..9b4090100da 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js.es6
+++ b/app/assets/javascripts/environments/stores/environments_store.js.es6
@@ -10,6 +10,8 @@
this.state.environments = [];
this.state.stoppedCounter = 0;
this.state.availableCounter = 0;
+ this.state.visibility = 'available';
+ this.state.filteredEnvironments = [];
return this;
},
@@ -59,7 +61,7 @@
if (occurs.length) {
acc[acc.indexOf(occurs[0])].children.push(environment);
- acc[acc.indexOf(occurs[0])].children.sort(this.sortByName);
+ acc[acc.indexOf(occurs[0])].children.slice().sort(this.sortByName);
} else {
acc.push({
name: environment.environment_type,
@@ -73,13 +75,70 @@
}
return acc;
- }, []).sort(this.sortByName);
+ }, []).slice().sort(this.sortByName);
this.state.environments = environmentsTree;
+ this.filterEnvironmentsByVisibility(this.state.environments);
+
return environmentsTree;
},
+ storeVisibility(visibility) {
+ this.state.visibility = visibility;
+ },
+ /**
+ * Given the visibility prop provided by the url query parameter and which
+ * changes according to the active tab we need to filter which environments
+ * should be visible.
+ *
+ * The environments array is a recursive tree structure and we need to filter
+ * both root level environments and children environments.
+ *
+ * In order to acomplish that, both `filterState` and `filterEnvironmentsByVisibility`
+ * functions work together.
+ * The first one works as the filter that verifies if the given environment matches
+ * the given state.
+ * The second guarantees both root level and children elements are filtered as well.
+ *
+ * Given array of environments will return only
+ * the environments that match the state stored.
+ *
+ * @param {Array} array
+ * @return {Array}
+ */
+ filterEnvironmentsByVisibility(arr) {
+ const filteredEnvironments = arr.map((item) => {
+ if (item.children) {
+ const filteredChildren = this.filterEnvironmentsByVisibility(
+ item.children,
+ ).filter(Boolean);
+
+ if (filteredChildren.length) {
+ item.children = filteredChildren;
+ return item;
+ }
+ }
+
+ return this.filterState(this.state.visibility, item);
+ }).filter(Boolean);
+
+ this.state.filteredEnvironments = filteredEnvironments;
+ return filteredEnvironments;
+ },
+
+ /**
+ * Given the state and the environment,
+ * returns only if the environment state matches the one provided.
+ *
+ * @param {String} state
+ * @param {Object} environment
+ * @return {Object}
+ */
+ filterState(state, environment) {
+ return environment.state === state && environment;
+ },
+
/**
* Toggles folder open property given the environment type.
*
diff --git a/app/assets/javascripts/extensions/array.js.es6 b/app/assets/javascripts/extensions/array.js.es6
index 717566a4715..cd401277689 100644
--- a/app/assets/javascripts/extensions/array.js.es6
+++ b/app/assets/javascripts/extensions/array.js.es6
@@ -1,11 +1,11 @@
-/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */
+/* eslint-disable no-extend-native, func-names, space-before-function-paren, space-infix-ops, max-len */
Array.prototype.first = function() {
return this[0];
-}
+};
Array.prototype.last = function() {
return this[this.length-1];
-}
+};
Array.prototype.find = Array.prototype.find || function(predicate, ...args) {
if (!this) throw new TypeError('Array.prototype.find called on null or undefined');
diff --git a/app/assets/javascripts/extensions/custom_event.js.es6 b/app/assets/javascripts/extensions/custom_event.js.es6
new file mode 100644
index 00000000000..abedae4c1c7
--- /dev/null
+++ b/app/assets/javascripts/extensions/custom_event.js.es6
@@ -0,0 +1,12 @@
+/* global CustomEvent */
+/* eslint-disable no-global-assign */
+
+// Custom event support for IE
+CustomEvent = function CustomEvent(event, parameters) {
+ const params = parameters || { bubbles: false, cancelable: false, detail: undefined };
+ const evt = document.createEvent('CustomEvent');
+ evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
+ return evt;
+};
+
+CustomEvent.prototype = window.Event.prototype;
diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js.es6
index 3f12ad9ff9f..90ab79305a7 100644
--- a/app/assets/javascripts/extensions/element.js.es6
+++ b/app/assets/javascripts/extensions/element.js.es6
@@ -1,5 +1,5 @@
/* global Element */
-/* eslint-disable consistent-return, max-len, no-empty, no-plusplus, func-names */
+/* eslint-disable consistent-return, max-len, no-empty, func-names */
Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) {
if (!selectedElement) return;
@@ -14,7 +14,7 @@ Element.prototype.matches = Element.prototype.matches ||
Element.prototype.webkitMatchesSelector ||
function (s) {
const matches = (this.document || this.ownerDocument).querySelectorAll(s);
- let i = matches.length;
- while (--i >= 0 && matches.item(i) !== this) {}
+ let i = matches.length - 1;
+ while (i >= 0 && matches.item(i) !== this) { i -= 1; }
return i > -1;
};
diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js
index cdedc865d1b..d3b58b2707a 100644
--- a/app/assets/javascripts/extensions/jquery.js
+++ b/app/assets/javascripts/extensions/jquery.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, object-shorthand, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, object-shorthand, comma-dangle, max-len */
// Disable an element and add the 'disabled' Bootstrap class
(function() {
$.fn.extend({
@@ -13,5 +13,4 @@
return $(this).removeAttr('disabled').removeClass('disabled');
}
});
-
}).call(this);
diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js
index 785f2869970..895a872568d 100644
--- a/app/assets/javascripts/files_comment_button.js
+++ b/app/assets/javascripts/files_comment_button.js
@@ -1,8 +1,8 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, padded-blocks, consistent-return */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len, one-var, one-var-declaration-per-line, quotes, prefer-template, newline-per-chained-call, comma-dangle, new-cap, no-else-return, consistent-return */
/* global FilesCommentButton */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.FilesCommentButton = (function() {
var COMMENT_BUTTON_CLASS, COMMENT_BUTTON_TEMPLATE, DEBOUNCE_TIMEOUT_DURATION, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
@@ -132,7 +132,6 @@
};
return FilesCommentButton;
-
})();
$.fn.filesCommentButton = function() {
@@ -145,5 +144,4 @@
}
});
};
-
}).call(this);
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
new file mode 100644
index 00000000000..7d297b8eee8
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
@@ -0,0 +1,69 @@
+/*= require filtered_search/filtered_search_dropdown */
+
+/* global droplabFilter */
+
+(() => {
+ class DropdownHint extends gl.FilteredSearchDropdown {
+ constructor(droplab, dropdown, input, filter) {
+ super(droplab, dropdown, input, filter);
+ this.config = {
+ droplabFilter: {
+ template: 'hint',
+ filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
+ },
+ };
+ }
+
+ itemClicked(e) {
+ const { selected } = e.detail;
+
+ if (selected.tagName === 'LI') {
+ if (selected.hasAttribute('data-value')) {
+ this.dismissDropdown();
+ } else if (selected.getAttribute('data-action') === 'submit') {
+ this.dismissDropdown();
+ this.dispatchFormSubmitEvent();
+ } else {
+ const token = selected.querySelector('.js-filter-hint').innerText.trim();
+ const tag = selected.querySelector('.js-filter-tag').innerText.trim();
+
+ if (tag.length) {
+ gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''));
+ }
+ this.dismissDropdown();
+ this.dispatchInputEvent();
+ }
+ }
+ }
+
+ renderContent() {
+ const dropdownData = [{
+ icon: 'fa-pencil',
+ hint: 'author:',
+ tag: '&lt;@author&gt;',
+ }, {
+ icon: 'fa-user',
+ hint: 'assignee:',
+ tag: '&lt;@assignee&gt;',
+ }, {
+ icon: 'fa-clock-o',
+ hint: 'milestone:',
+ tag: '&lt;%milestone&gt;',
+ }, {
+ icon: 'fa-tag',
+ hint: 'label:',
+ tag: '&lt;~label&gt;',
+ }];
+
+ this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config);
+ this.droplab.setData(this.hookId, dropdownData);
+ }
+
+ init() {
+ this.droplab.addHook(this.input, this.dropdown, [droplabFilter], this.config).init();
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.DropdownHint = DropdownHint;
+})();
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6
new file mode 100644
index 00000000000..13cbec1be4a
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6
@@ -0,0 +1,44 @@
+/*= require filtered_search/filtered_search_dropdown */
+
+/* global droplabAjax */
+/* global droplabFilter */
+
+(() => {
+ class DropdownNonUser extends gl.FilteredSearchDropdown {
+ constructor(droplab, dropdown, input, filter, endpoint, symbol) {
+ super(droplab, dropdown, input, filter);
+ this.symbol = symbol;
+ this.config = {
+ droplabAjax: {
+ endpoint,
+ method: 'setData',
+ loadingTemplate: this.loadingTemplate,
+ },
+ droplabFilter: {
+ filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
+ },
+ };
+ }
+
+ itemClicked(e) {
+ super.itemClicked(e, (selected) => {
+ const title = selected.querySelector('.js-data-value').innerText.trim();
+ return `${this.symbol}${gl.DropdownUtils.getEscapedText(title)}`;
+ });
+ }
+
+ renderContent(forceShowList = false) {
+ this.droplab
+ .changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config);
+ super.renderContent(forceShowList);
+ }
+
+ init() {
+ this.droplab
+ .addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init();
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.DropdownNonUser = DropdownNonUser;
+})();
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6
new file mode 100644
index 00000000000..7bf199d9274
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6
@@ -0,0 +1,53 @@
+/*= require filtered_search/filtered_search_dropdown */
+
+/* global droplabAjaxFilter */
+
+(() => {
+ class DropdownUser extends gl.FilteredSearchDropdown {
+ constructor(droplab, dropdown, input, filter) {
+ super(droplab, dropdown, input, filter);
+ this.config = {
+ droplabAjaxFilter: {
+ endpoint: '/autocomplete/users.json',
+ searchKey: 'search',
+ params: {
+ per_page: 20,
+ active: true,
+ project_id: this.getProjectId(),
+ current_user: true,
+ },
+ searchValueFunction: this.getSearchInput.bind(this),
+ loadingTemplate: this.loadingTemplate,
+ },
+ };
+ }
+
+ itemClicked(e) {
+ super.itemClicked(e,
+ selected => selected.querySelector('.dropdown-light-content').innerText.trim());
+ }
+
+ renderContent(forceShowList = false) {
+ this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config);
+ super.renderContent(forceShowList);
+ }
+
+ getProjectId() {
+ return this.input.getAttribute('data-project-id');
+ }
+
+ getSearchInput() {
+ const query = gl.DropdownUtils.getSearchInput(this.input);
+ const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
+
+ return lastToken.value || '';
+ }
+
+ init() {
+ this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init();
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.DropdownUser = DropdownUser;
+})();
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6
new file mode 100644
index 00000000000..de3fa116717
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6
@@ -0,0 +1,126 @@
+(() => {
+ class DropdownUtils {
+ static getEscapedText(text) {
+ let escapedText = text;
+ const hasSpace = text.indexOf(' ') !== -1;
+ const hasDoubleQuote = text.indexOf('"') !== -1;
+
+ // Encapsulate value with quotes if it has spaces
+ // Known side effect: values's with both single and double quotes
+ // won't escape properly
+ if (hasSpace) {
+ if (hasDoubleQuote) {
+ escapedText = `'${text}'`;
+ } else {
+ // Encapsulate singleQuotes or if it hasSpace
+ escapedText = `"${text}"`;
+ }
+ }
+
+ return escapedText;
+ }
+
+ static filterWithSymbol(filterSymbol, input, item) {
+ const updatedItem = item;
+ const query = gl.DropdownUtils.getSearchInput(input);
+ const { lastToken, searchToken } = gl.FilteredSearchTokenizer.processTokens(query);
+
+ if (lastToken !== searchToken) {
+ const title = updatedItem.title.toLowerCase();
+ let value = lastToken.value.toLowerCase();
+
+ // 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) {
+ value = value.slice(1);
+ }
+
+ // Eg. filterSymbol = ~ for labels
+ const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1;
+ const match = title.indexOf(`${lastToken.symbol}${value}`) !== -1;
+
+ updatedItem.droplab_hidden = !match && !matchWithoutSymbol;
+ } else {
+ updatedItem.droplab_hidden = false;
+ }
+
+ return updatedItem;
+ }
+
+ static filterHint(input, item) {
+ const updatedItem = item;
+ const query = gl.DropdownUtils.getSearchInput(input);
+ let { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
+ lastToken = lastToken.key || lastToken || '';
+
+ if (!lastToken || query.split('').last() === ' ') {
+ updatedItem.droplab_hidden = false;
+ } else if (lastToken) {
+ const split = lastToken.split(':');
+ const tokenName = split[0].split(' ').last();
+
+ const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
+ updatedItem.droplab_hidden = tokenName ? match : false;
+ }
+
+ return updatedItem;
+ }
+
+ static setDataValueIfSelected(filter, selected) {
+ const dataValue = selected.getAttribute('data-value');
+
+ if (dataValue) {
+ gl.FilteredSearchDropdownManager.addWordToInput(filter, dataValue);
+ }
+
+ // Return boolean based on whether it was set
+ return dataValue !== null;
+ }
+
+ static getSearchInput(filteredSearchInput) {
+ const inputValue = filteredSearchInput.value;
+ const { right } = gl.DropdownUtils.getInputSelectionPosition(filteredSearchInput);
+
+ return inputValue.slice(0, right);
+ }
+
+ static getInputSelectionPosition(input) {
+ const selectionStart = input.selectionStart;
+ let inputValue = input.value;
+ // 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, '_'));
+
+ // Get the right position for the word selected
+ // Regex matches first space
+ let right = inputValue.slice(selectionStart).search(/\s/);
+
+ if (right >= 0) {
+ right += selectionStart;
+ } else if (right < 0) {
+ right = inputValue.length;
+ }
+
+ // Get the left position for the word selected
+ // Regex matches last non-whitespace character
+ let left = inputValue.slice(0, right).search(/\S+$/);
+
+ if (selectionStart === 0) {
+ left = 0;
+ } else if (selectionStart === inputValue.length && left < 0) {
+ left = inputValue.length;
+ } else if (left < 0) {
+ left = selectionStart;
+ }
+
+ return {
+ left,
+ right,
+ };
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.DropdownUtils = DropdownUtils;
+})();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js
new file mode 100644
index 00000000000..d188718c5f3
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js
@@ -0,0 +1,7 @@
+ // This is a manifest file that'll be compiled into including all the files listed below.
+ // Add new JavaScript code in separate files in this directory and they'll automatically
+ // be included in the compiled file accessible from http://example.com/assets/application.js
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+ // the compiled file.
+ //
+ /*= require_tree . */
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
new file mode 100644
index 00000000000..859d6515531
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
@@ -0,0 +1,112 @@
+(() => {
+ const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
+
+ class FilteredSearchDropdown {
+ constructor(droplab, dropdown, input, filter) {
+ this.droplab = droplab;
+ this.hookId = input.getAttribute('data-id');
+ this.input = input;
+ this.filter = filter;
+ this.dropdown = dropdown;
+ this.loadingTemplate = `<div class="filter-dropdown-loading">
+ <i class="fa fa-spinner fa-spin"></i>
+ </div>`;
+ this.bindEvents();
+ }
+
+ bindEvents() {
+ this.itemClickedWrapper = this.itemClicked.bind(this);
+ this.dropdown.addEventListener('click.dl', this.itemClickedWrapper);
+ }
+
+ unbindEvents() {
+ this.dropdown.removeEventListener('click.dl', this.itemClickedWrapper);
+ }
+
+ getCurrentHook() {
+ return this.droplab.hooks.filter(h => h.id === this.hookId)[0] || null;
+ }
+
+ itemClicked(e, getValueFunction) {
+ const { selected } = e.detail;
+
+ if (selected.tagName === 'LI' && selected.innerHTML) {
+ const dataValueSet = gl.DropdownUtils.setDataValueIfSelected(this.filter, selected);
+
+ if (!dataValueSet) {
+ const value = getValueFunction(selected);
+ gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value);
+ }
+
+ this.dismissDropdown();
+ this.dispatchInputEvent();
+ }
+ }
+
+ setAsDropdown() {
+ this.input.setAttribute(DATA_DROPDOWN_TRIGGER, `#${this.dropdown.id}`);
+ }
+
+ setOffset(offset = 0) {
+ this.dropdown.style.left = `${offset}px`;
+ }
+
+ renderContent(forceShowList = false) {
+ if (forceShowList && this.getCurrentHook().list.hidden) {
+ this.getCurrentHook().list.show();
+ }
+ }
+
+ render(forceRenderContent = false, forceShowList = false) {
+ this.setAsDropdown();
+
+ const currentHook = this.getCurrentHook();
+ const firstTimeInitialized = currentHook === null;
+
+ if (firstTimeInitialized || forceRenderContent) {
+ this.renderContent(forceShowList);
+ } else if (currentHook.list.list.id !== this.dropdown.id) {
+ this.renderContent(forceShowList);
+ }
+ }
+
+ dismissDropdown() {
+ // Focusing on the input will dismiss dropdown
+ // (default droplab functionality)
+ this.input.focus();
+ }
+
+ 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,
+ }));
+ }
+
+ dispatchFormSubmitEvent() {
+ // dispatchEvent() is necessary as form.submit() does not
+ // trigger event handlers
+ this.input.form.dispatchEvent(new Event('submit'));
+ }
+
+ hideDropdown() {
+ this.getCurrentHook().list.hide();
+ }
+
+ resetFilters() {
+ const hook = this.getCurrentHook();
+ const data = hook.list.data;
+ const results = data.map((o) => {
+ const updated = o;
+ updated.droplab_hidden = false;
+ return updated;
+ });
+ hook.list.render(results);
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.FilteredSearchDropdown = FilteredSearchDropdown;
+})();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
new file mode 100644
index 00000000000..00e1c28692f
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
@@ -0,0 +1,207 @@
+/* global DropLab */
+
+(() => {
+ class FilteredSearchDropdownManager {
+ constructor() {
+ this.tokenizer = gl.FilteredSearchTokenizer;
+ this.filteredSearchInput = document.querySelector('.filtered-search');
+
+ this.setupMapping();
+
+ this.cleanupWrapper = this.cleanup.bind(this);
+ document.addEventListener('page:fetch', this.cleanupWrapper);
+ }
+
+ cleanup() {
+ if (this.droplab) {
+ this.droplab.destroy();
+ this.droplab = null;
+ }
+
+ this.setupMapping();
+
+ document.removeEventListener('page:fetch', this.cleanupWrapper);
+ }
+
+ setupMapping() {
+ this.mapping = {
+ author: {
+ reference: null,
+ gl: 'DropdownUser',
+ element: document.querySelector('#js-dropdown-author'),
+ },
+ assignee: {
+ reference: null,
+ gl: 'DropdownUser',
+ element: document.querySelector('#js-dropdown-assignee'),
+ },
+ milestone: {
+ reference: null,
+ gl: 'DropdownNonUser',
+ extraArguments: ['milestones.json', '%'],
+ element: document.querySelector('#js-dropdown-milestone'),
+ },
+ label: {
+ reference: null,
+ gl: 'DropdownNonUser',
+ extraArguments: ['labels.json', '~'],
+ element: document.querySelector('#js-dropdown-label'),
+ },
+ hint: {
+ reference: null,
+ gl: 'DropdownHint',
+ element: document.querySelector('#js-dropdown-hint'),
+ },
+ };
+ }
+
+ static addWordToInput(tokenName, tokenValue = '') {
+ const input = document.querySelector('.filtered-search');
+ const inputValue = input.value;
+ const word = `${tokenName}:${tokenValue}`;
+
+ // Get the string to replace
+ let newCaretPosition = input.selectionStart;
+ const { left, right } = gl.DropdownUtils.getInputSelectionPosition(input);
+
+ input.value = `${inputValue.substr(0, left)}${word}${inputValue.substr(right)}`;
+
+ // If we have added a tokenValue at the end of the input,
+ // add a space and set selection to the end
+ if (right >= inputValue.length && tokenValue !== '') {
+ input.value += ' ';
+ newCaretPosition = input.value.length;
+ }
+
+ gl.FilteredSearchDropdownManager.updateInputCaretPosition(newCaretPosition, input);
+ }
+
+ static updateInputCaretPosition(selectionStart, input) {
+ // Reset the position
+ // Sometimes can end up at end of input
+ input.setSelectionRange(selectionStart, selectionStart);
+
+ const { right } = gl.DropdownUtils.getInputSelectionPosition(input);
+
+ input.setSelectionRange(right, right);
+ }
+
+ updateCurrentDropdownOffset() {
+ this.updateDropdownOffset(this.currentDropdown);
+ }
+
+ updateDropdownOffset(key) {
+ if (!this.font) {
+ this.font = window.getComputedStyle(this.filteredSearchInput).font;
+ }
+
+ const input = this.filteredSearchInput;
+ const inputText = input.value.slice(0, input.selectionStart);
+ const filterIconPadding = 27;
+ let offset = gl.text.getTextWidth(inputText, this.font) + filterIconPadding;
+
+ const currentDropdownWidth = this.mapping[key].element.clientWidth === 0 ? 200 :
+ this.mapping[key].element.clientWidth;
+ const offsetMaxWidth = this.filteredSearchInput.clientWidth - currentDropdownWidth;
+
+ if (offsetMaxWidth < offset) {
+ offset = offsetMaxWidth;
+ }
+
+ this.mapping[key].reference.setOffset(offset);
+ }
+
+ load(key, firstLoad = false) {
+ const mappingKey = this.mapping[key];
+ const glClass = mappingKey.gl;
+ const element = mappingKey.element;
+ let forceShowList = false;
+
+ if (!mappingKey.reference) {
+ const dl = this.droplab;
+ const defaultArguments = [null, dl, element, this.filteredSearchInput, key];
+ const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
+
+ // Passing glArguments to `new gl[glClass](<arguments>)`
+ mappingKey.reference = new (Function.prototype.bind.apply(gl[glClass], glArguments))();
+ }
+
+ if (firstLoad) {
+ mappingKey.reference.init();
+ }
+
+ if (this.currentDropdown === 'hint') {
+ // Force the dropdown to show if it was clicked from the hint dropdown
+ forceShowList = true;
+ }
+
+ this.updateDropdownOffset(key);
+ mappingKey.reference.render(firstLoad, forceShowList);
+
+ this.currentDropdown = key;
+ }
+
+ loadDropdown(dropdownName = '') {
+ let firstLoad = false;
+
+ if (!this.droplab) {
+ firstLoad = true;
+ this.droplab = new DropLab();
+ }
+
+ const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase());
+ const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key
+ && this.mapping[match.key];
+ const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint';
+
+ if (shouldOpenFilterDropdown || shouldOpenHintDropdown) {
+ const key = match && match.key ? match.key : 'hint';
+ this.load(key, firstLoad);
+ }
+ }
+
+ setDropdown() {
+ const { lastToken, searchToken } = this.tokenizer
+ .processTokens(gl.DropdownUtils.getSearchInput(this.filteredSearchInput));
+
+ if (this.currentDropdown) {
+ this.updateCurrentDropdownOffset();
+ }
+
+ if (lastToken === searchToken && lastToken !== null) {
+ // Token is not fully initialized yet because it has no value
+ // Eg. token = 'label:'
+
+ const split = lastToken.split(':');
+ const dropdownName = split[0].split(' ').last();
+ this.loadDropdown(split.length > 1 ? dropdownName : '');
+ } else if (lastToken) {
+ // Token has been initialized into an object because it has a value
+ this.loadDropdown(lastToken.key);
+ } else {
+ this.loadDropdown('hint');
+ }
+ }
+
+ resetDropdowns() {
+ // Force current dropdown to hide
+ this.mapping[this.currentDropdown].reference.hideDropdown();
+
+ // Re-Load dropdown
+ this.setDropdown();
+
+ // Reset filters for current dropdown
+ this.mapping[this.currentDropdown].reference.resetFilters();
+
+ // Reposition dropdown so that it is aligned with cursor
+ this.updateDropdownOffset(this.currentDropdown);
+ }
+
+ destroyDroplab() {
+ this.droplab.destroy();
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager;
+})();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
new file mode 100644
index 00000000000..029564ffc61
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
@@ -0,0 +1,230 @@
+/* global Turbolinks */
+
+(() => {
+ class FilteredSearchManager {
+ constructor() {
+ this.filteredSearchInput = document.querySelector('.filtered-search');
+ this.clearSearchButton = document.querySelector('.clear-search');
+
+ if (this.filteredSearchInput) {
+ this.tokenizer = gl.FilteredSearchTokenizer;
+ this.dropdownManager = new gl.FilteredSearchDropdownManager();
+
+ this.bindEvents();
+ this.loadSearchParamsFromURL();
+ this.dropdownManager.setDropdown();
+
+ this.cleanupWrapper = this.cleanup.bind(this);
+ document.addEventListener('page:fetch', this.cleanupWrapper);
+ }
+ }
+
+ cleanup() {
+ this.unbindEvents();
+ document.removeEventListener('page:fetch', this.cleanupWrapper);
+ }
+
+ bindEvents() {
+ this.handleFormSubmit = this.handleFormSubmit.bind(this);
+ this.setDropdownWrapper = this.dropdownManager.setDropdown.bind(this.dropdownManager);
+ this.toggleClearSearchButtonWrapper = this.toggleClearSearchButton.bind(this);
+ this.checkForEnterWrapper = this.checkForEnter.bind(this);
+ this.clearSearchWrapper = this.clearSearch.bind(this);
+ this.checkForBackspaceWrapper = this.checkForBackspace.bind(this);
+ this.tokenChange = this.tokenChange.bind(this);
+
+ this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit);
+ this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
+ this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
+ this.filteredSearchInput.addEventListener('keydown', this.checkForEnterWrapper);
+ this.filteredSearchInput.addEventListener('keyup', this.checkForBackspaceWrapper);
+ this.filteredSearchInput.addEventListener('click', this.tokenChange);
+ this.filteredSearchInput.addEventListener('keyup', this.tokenChange);
+ this.clearSearchButton.addEventListener('click', this.clearSearchWrapper);
+ }
+
+ unbindEvents() {
+ this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit);
+ this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
+ this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
+ this.filteredSearchInput.removeEventListener('keydown', this.checkForEnterWrapper);
+ this.filteredSearchInput.removeEventListener('keyup', this.checkForBackspaceWrapper);
+ this.filteredSearchInput.removeEventListener('click', this.tokenChange);
+ this.filteredSearchInput.removeEventListener('keyup', this.tokenChange);
+ this.clearSearchButton.removeEventListener('click', this.clearSearchWrapper);
+ }
+
+ checkForBackspace(e) {
+ // 8 = Backspace Key
+ // 46 = Delete Key
+ if (e.keyCode === 8 || e.keyCode === 46) {
+ // Reposition dropdown so that it is aligned with cursor
+ this.dropdownManager.updateCurrentDropdownOffset();
+ }
+ }
+
+ checkForEnter(e) {
+ if (e.keyCode === 38 || e.keyCode === 40) {
+ const selectionStart = this.filteredSearchInput.selectionStart;
+
+ e.preventDefault();
+ this.filteredSearchInput.setSelectionRange(selectionStart, selectionStart);
+ }
+
+ if (e.keyCode === 13) {
+ const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
+ const dropdownEl = dropdown.element;
+ const activeElements = dropdownEl.querySelectorAll('.dropdown-active');
+
+ e.preventDefault();
+
+ if (!activeElements.length) {
+ // Prevent droplab from opening dropdown
+ this.dropdownManager.destroyDroplab();
+
+ this.search();
+ }
+ }
+ }
+
+ toggleClearSearchButton(e) {
+ if (e.target.value) {
+ this.clearSearchButton.classList.remove('hidden');
+ } else {
+ this.clearSearchButton.classList.add('hidden');
+ }
+ }
+
+ clearSearch(e) {
+ e.preventDefault();
+
+ this.filteredSearchInput.value = '';
+ this.clearSearchButton.classList.add('hidden');
+
+ this.dropdownManager.resetDropdowns();
+ }
+
+ handleFormSubmit(e) {
+ e.preventDefault();
+ this.search();
+ }
+
+ loadSearchParamsFromURL() {
+ const params = gl.utils.getUrlParamsArray();
+ const usernameParams = this.getUsernameParams();
+ const inputValues = [];
+
+ params.forEach((p) => {
+ const split = p.split('=');
+ const keyParam = decodeURIComponent(split[0]);
+ const value = split[1];
+
+ // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys
+ const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(p);
+
+ if (condition) {
+ inputValues.push(`${condition.tokenKey}:${condition.value}`);
+ } else {
+ // Sanitize value since URL converts spaces into +
+ // Replace before decode so that we know what was originally + versus the encoded +
+ const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value;
+ const match = gl.FilteredSearchTokenKeys.searchByKeyParam(keyParam);
+
+ if (match) {
+ const indexOf = keyParam.indexOf('_');
+ const sanitizedKey = indexOf !== -1 ? keyParam.slice(0, keyParam.indexOf('_')) : keyParam;
+ const symbol = match.symbol;
+ let quotationsToUse = '';
+
+ if (sanitizedValue.indexOf(' ') !== -1) {
+ // Prefer ", but use ' if required
+ quotationsToUse = sanitizedValue.indexOf('"') === -1 ? '"' : '\'';
+ }
+
+ inputValues.push(`${sanitizedKey}:${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`);
+ } else if (!match && keyParam === 'assignee_id') {
+ const id = parseInt(value, 10);
+ if (usernameParams[id]) {
+ inputValues.push(`assignee:@${usernameParams[id]}`);
+ }
+ } else if (!match && keyParam === 'author_id') {
+ const id = parseInt(value, 10);
+ if (usernameParams[id]) {
+ inputValues.push(`author:@${usernameParams[id]}`);
+ }
+ } else if (!match && keyParam === 'search') {
+ inputValues.push(sanitizedValue);
+ }
+ }
+ });
+
+ // Trim the last space value
+ this.filteredSearchInput.value = inputValues.join(' ');
+
+ if (inputValues.length > 0) {
+ this.clearSearchButton.classList.remove('hidden');
+ }
+ }
+
+ search() {
+ const paths = [];
+ const { tokens, searchToken } = this.tokenizer.processTokens(this.filteredSearchInput.value);
+ const currentState = gl.utils.getParameterByName('state') || 'opened';
+ paths.push(`state=${currentState}`);
+
+ tokens.forEach((token) => {
+ const condition = gl.FilteredSearchTokenKeys
+ .searchByConditionKeyValue(token.key, token.value.toLowerCase());
+ const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key);
+ const keyParam = param ? `${token.key}_${param}` : token.key;
+ let tokenPath = '';
+
+ if (condition) {
+ tokenPath = condition.url;
+ } else {
+ let tokenValue = token.value;
+
+ if ((tokenValue[0] === '\'' && tokenValue[tokenValue.length - 1] === '\'') ||
+ (tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')) {
+ tokenValue = tokenValue.slice(1, tokenValue.length - 1);
+ }
+
+ tokenPath = `${keyParam}=${encodeURIComponent(tokenValue)}`;
+ }
+
+ paths.push(tokenPath);
+ });
+
+ if (searchToken) {
+ const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+');
+ paths.push(`search=${sanitized}`);
+ }
+
+ Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`);
+ }
+
+ getUsernameParams() {
+ const usernamesById = {};
+ try {
+ const attribute = this.filteredSearchInput.getAttribute('data-username-params');
+ JSON.parse(attribute).forEach((user) => {
+ usernamesById[user.id] = user.username;
+ });
+ } catch (e) {
+ // do nothing
+ }
+ return usernamesById;
+ }
+
+ tokenChange() {
+ const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
+ const currentDropdownRef = dropdown.reference;
+
+ this.setDropdownWrapper();
+ currentDropdownRef.dispatchInputEvent();
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.FilteredSearchManager = FilteredSearchManager;
+})();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6
new file mode 100644
index 00000000000..e6b53cd4b55
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6
@@ -0,0 +1,96 @@
+(() => {
+ const tokenKeys = [{
+ key: 'author',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+ }, {
+ key: 'assignee',
+ type: 'string',
+ param: 'username',
+ symbol: '@',
+ }, {
+ key: 'milestone',
+ type: 'string',
+ param: 'title',
+ symbol: '%',
+ }, {
+ key: 'label',
+ type: 'array',
+ param: 'name[]',
+ symbol: '~',
+ }];
+
+ const alternativeTokenKeys = [{
+ key: 'label',
+ type: 'string',
+ param: 'name',
+ symbol: '~',
+ }];
+
+ const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
+
+ 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: 'label_name[]=No+Label',
+ tokenKey: 'label',
+ value: 'none',
+ }];
+
+ class FilteredSearchTokenKeys {
+ static get() {
+ return tokenKeys;
+ }
+
+ static getAlternatives() {
+ return alternativeTokenKeys;
+ }
+
+ static getConditions() {
+ return conditions;
+ }
+
+ static searchByKey(key) {
+ return tokenKeys.find(tokenKey => tokenKey.key === key) || null;
+ }
+
+ static searchBySymbol(symbol) {
+ return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null;
+ }
+
+ static searchByKeyParam(keyParam) {
+ return tokenKeysWithAlternative.find((tokenKey) => {
+ let tokenKeyParam = tokenKey.key;
+
+ if (tokenKey.param) {
+ tokenKeyParam += `_${tokenKey.param}`;
+ }
+
+ return keyParam === tokenKeyParam;
+ }) || null;
+ }
+
+ static searchByConditionUrl(url) {
+ return conditions.find(condition => condition.url === url) || null;
+ }
+
+ static searchByConditionKeyValue(key, value) {
+ return conditions
+ .find(condition => condition.tokenKey === key && condition.value === value) || null;
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
+})();
diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
new file mode 100644
index 00000000000..cf53845a48b
--- /dev/null
+++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
@@ -0,0 +1,45 @@
+(() => {
+ class FilteredSearchTokenizer {
+ static processTokens(input) {
+ // Regex extracts `(token):(symbol)(value)`
+ // Values that start with a double quote must end in a double quote (same for single)
+ const tokenRegex = /(\w+):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\S+))/g;
+ const tokens = [];
+ let lastToken = null;
+ const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
+ let tokenValue = v1 || v2 || v3;
+ let tokenSymbol = symbol;
+
+ if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
+ tokenSymbol = tokenValue;
+ tokenValue = '';
+ }
+
+ 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;
+ } else {
+ lastToken = searchToken;
+ }
+
+ return {
+ tokens,
+ lastToken,
+ searchToken,
+ };
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
+})();
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index 804d7d9c4ab..249fe23d4cb 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, max-len */
(function() {
this.Flash = (function() {
var hideFlash;
@@ -38,7 +38,5 @@
}
return Flash;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 87c579ac757..3f23095dad9 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-template-curly-in-string, comma-dangle, object-shorthand, quotes, dot-notation, no-else-return, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-param-reassign, no-useless-escape, prefer-template, consistent-return, wrap-iife, prefer-arrow-callback, camelcase, no-unused-vars, no-useless-return, padded-blocks, vars-on-top, indent, no-extra-semi, no-multi-spaces, semi, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-template-curly-in-string, comma-dangle, object-shorthand, quotes, dot-notation, no-else-return, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-param-reassign, no-useless-escape, prefer-template, consistent-return, wrap-iife, prefer-arrow-callback, camelcase, no-unused-vars, no-useless-return, vars-on-top, max-len */
// Creates the variables for setting up GFM auto-completion
(function() {
@@ -48,18 +48,18 @@
},
DefaultOptions: {
sorter: function(query, items, searchKey) {
+ this.setting.highlightFirst = this.setting.alwaysHighlightFirst || query.length > 0;
if (gl.GfmAutoComplete.isLoading(items)) {
+ this.setting.highlightFirst = false;
return items;
}
return $.fn.atwho["default"].callbacks.sorter(query, items, searchKey);
},
filter: function(query, data, searchKey) {
if (gl.GfmAutoComplete.isLoading(data)) {
- gl.GfmAutoComplete.togglePreventSelection.call(this, true);
gl.GfmAutoComplete.fetchData(this.$inputor, this.at);
return data;
} else {
- gl.GfmAutoComplete.togglePreventSelection.call(this, false);
return $.fn.atwho["default"].callbacks.filter(query, data, searchKey);
}
},
@@ -154,7 +154,7 @@
return {
username: m.username,
- avatarTag: autoCompleteAvatar.length === 1 ? txtAvatar : imgAvatar,
+ avatarTag: autoCompleteAvatar.length === 1 ? txtAvatar : imgAvatar,
title: sanitize(title),
search: sanitize(m.username + " " + m.name)
};
@@ -257,9 +257,9 @@
insertTpl: '${atwho-at}${title}',
callbacks: {
matcher: this.DefaultOptions.matcher,
- sorter: this.DefaultOptions.sorter,
beforeInsert: this.DefaultOptions.beforeInsert,
filter: this.DefaultOptions.filter,
+ sorter: this.DefaultOptions.sorter,
beforeSave: function(merges) {
if (gl.GfmAutoComplete.isLoading(merges)) return merges;
var sanitizeLabelTitle;
@@ -335,7 +335,7 @@
});
},
matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
- var regexp = /(?:^|\n)\/([A-Za-z_]*)$/gi
+ var regexp = /(?:^|\n)\/([A-Za-z_]*)$/gi;
var match = regexp.exec(subtext);
if (match) {
return match[1];
@@ -367,14 +367,14 @@
return $input.trigger('keyup');
},
isLoading(data) {
- if (!data || !data.length) return false;
- if (Array.isArray(data)) data = data[0];
- return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0];
- },
- togglePreventSelection(isPrevented = !!this.setting.tabSelectsMatch) {
- this.setting.tabSelectsMatch = !isPrevented;
- this.setting.spaceSelectsMatch = !isPrevented;
- },
- };
+ var dataToInspect = data;
+ if (data && data.length > 0) {
+ dataToInspect = data[0];
+ }
+ var loadingState = this.defaultLoadingData[0];
+ return dataToInspect &&
+ (dataToInspect === loadingState || dataToInspect.name === loadingState);
+ }
+ };
}).call(this);
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index bb516b3d2df..cc1c0877cdf 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -1,11 +1,11 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, space-before-blocks, prefer-rest-params, max-len, vars-on-top, no-plusplus, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, semi, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, padded-blocks, prefer-template, no-param-reassign, no-loop-func, no-extra-semi, keyword-spacing, no-mixed-operators */
+/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */
/* global fuzzaldrinPlus */
/* global Turbolinks */
(function() {
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
- bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
- indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+ bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
GitLabDropdownFilter = (function() {
var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
@@ -37,7 +37,7 @@
.on('keydown', function (e) {
var keyCode = e.which;
if (keyCode === 13 && !options.elIsInput) {
- e.preventDefault()
+ e.preventDefault();
}
})
.on('input', function() {
@@ -133,7 +133,6 @@
};
return GitLabDropdownFilter;
-
})();
GitLabDropdownRemote = (function() {
@@ -186,7 +185,6 @@
};
return GitLabDropdownRemote;
-
})();
GitLabDropdown = (function() {
@@ -206,7 +204,7 @@
SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
- CURSOR_SELECT_SCROLL_PADDING = 5
+ CURSOR_SELECT_SCROLL_PADDING = 5;
FILTER_INPUT = '.dropdown-input .dropdown-input-field';
@@ -223,7 +221,7 @@
this.dropdown = selector != null ? $(selector) : $(this.el).parent();
// Set Defaults
this.filterInput = this.options.filterInput || this.getElement(FILTER_INPUT);
- this.highlight = !!this.options.highlight
+ this.highlight = !!this.options.highlight;
this.filterInputBlur = this.options.filterInputBlur != null
? this.options.filterInputBlur
: true;
@@ -476,7 +474,7 @@
this.removeArrayKeyEvent();
$input = this.dropdown.find(".dropdown-input-field");
if (this.options.filterable) {
- $input.blur().val("");
+ $input.blur();
}
if (this.dropdown.find(".dropdown-toggle-page").length) {
$('.dropdown-menu', this.dropdown).removeClass(PAGE_TWO_CLASS);
@@ -494,7 +492,7 @@
} else {
var ul = document.createElement('ul');
- for (var i = 0; i < html.length; i++) {
+ for (var i = 0; i < html.length; i += 1) {
var el = html[i];
if (el instanceof jQuery) {
@@ -550,7 +548,7 @@
value = this.options.id ? this.options.id(data) : data.id;
fieldName = this.options.fieldName;
- if (value) { value = value.toString().replace(/'/g, '\\\'') };
+ if (value) { value = value.toString().replace(/'/g, '\\\''); }
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
if (field.length) {
@@ -641,7 +639,7 @@
: selectedObject.id;
if (isInput) {
field = $(this.el);
- } else if(value) {
+ } else if (value) {
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']");
}
@@ -653,18 +651,14 @@
isMarking = false;
el.removeClass(ACTIVE_CLASS);
if (field && field.length) {
- if (isInput) {
- field.val('');
- } else {
- field.remove();
- }
+ this.clearField(field, isInput);
}
} else if (el.hasClass(INDETERMINATE_CLASS)) {
isMarking = true;
el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS);
if (field && field.length && value == null) {
- field.remove();
+ this.clearField(field, isInput);
}
if ((!field || !field.length) && fieldName) {
this.addInput(fieldName, value, selectedObject);
@@ -678,7 +672,7 @@
}
}
if (field && field.length && value == null) {
- field.remove();
+ this.clearField(field, isInput);
}
// Toggle active class for the tick mark
el.addClass(ACTIVE_CLASS);
@@ -695,8 +689,8 @@
};
GitLabDropdown.prototype.focusTextInput = function() {
- if (this.options.filterable) { this.filterInput.focus() }
- }
+ if (this.options.filterable) { this.filterInput.focus(); }
+ };
GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
var $input;
@@ -802,7 +796,7 @@
listItemBottom = listItemTop + listItemHeight;
if (!index) {
// Scroll the dropdown content to the top
- $dropdownContent.scrollTop(0)
+ $dropdownContent.scrollTop(0);
} else if (index === ($listItems.length - 1)) {
// Scroll the dropdown content to the bottom
$dropdownContent.scrollTop($dropdownContent.prop('scrollHeight'));
@@ -828,8 +822,11 @@
return $(this.el).find(".dropdown-toggle-text").text(this.options.toggleLabel(selected, el, instance));
};
- return GitLabDropdown;
+ GitLabDropdown.prototype.clearField = function(field, isInput) {
+ return isInput ? field.val('') : field.remove();
+ };
+ return GitLabDropdown;
})();
$.fn.glDropdown = function(opts) {
@@ -839,5 +836,4 @@
}
});
};
-
}).call(this);
diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js.es6
index 63f9cafa8d0..16be930a2f4 100644
--- a/app/assets/javascripts/gl_field_errors.js.es6
+++ b/app/assets/javascripts/gl_field_errors.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign, padded-blocks */
+/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */
//= require gl_field_error
@@ -45,5 +45,4 @@
}
global.GlFieldErrors = GlFieldErrors;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 7dc2d13e5d8..08b2494f3df 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */
/* global GitLab */
/* global DropzoneInput */
/* global autosize */
@@ -35,8 +35,8 @@
autosize(this.textarea);
// form and textarea event listeners
this.addEventListeners();
- gl.text.init(this.form);
}
+ gl.text.init(this.form);
// hide discard button
this.form.find('.js-note-discard').hide();
return this.form.show();
@@ -58,7 +58,5 @@
};
return GLForm;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph.js b/app/assets/javascripts/graphs/stat_graph.js
index 3273bf3a263..2e6da5750de 100644
--- a/app/assets/javascripts/graphs/stat_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-return-assign, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-return-assign, max-len */
(function() {
this.StatGraph = (function() {
function StatGraph() {}
@@ -14,7 +14,5 @@
};
return StatGraph;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js
index 2d08a7c6ac3..73715286c4a 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign */
/* global ContributorsGraph */
/* global ContributorsAuthorGraph */
/* global ContributorsMasterGraph */
@@ -112,7 +112,5 @@
};
return ContributorsStatGraph;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
index 9c5e9381e52..cacfc177fc8 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js
@@ -1,11 +1,11 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, space-before-blocks, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, padded-blocks, newline-per-chained-call, no-else-return */
+/* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return */
/* global d3 */
/* global ContributorsGraph */
/*= require d3 */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
@@ -91,7 +91,6 @@
};
return ContributorsGraph;
-
})();
this.ContributorsMasterGraph = (function(superClass) {
@@ -196,7 +195,6 @@
};
return ContributorsMasterGraph;
-
})(ContributorsGraph);
this.ContributorsAuthorGraph = (function(superClass) {
@@ -274,7 +272,5 @@
};
return ContributorsAuthorGraph;
-
})(ContributorsGraph);
-
}).call(this);
diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
index 1982f4af939..29c3163328f 100644
--- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js
+++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, no-plusplus, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */
(function() {
window.ContributorsStatGraphUtil = {
parse_log: function(log) {
@@ -6,7 +6,7 @@
total = {};
by_author = {};
by_email = {};
- for (i = 0, len = log.length; i < len; i++) {
+ for (i = 0, len = log.length; i < len; i += 1) {
entry = log[i];
if (total[entry.date] == null) {
this.add_date(entry.date, total);
@@ -135,5 +135,4 @@
}
}
};
-
}).call(this);
diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js
index 17a76168a79..10dfd05fe3c 100644
--- a/app/assets/javascripts/group_avatar.js
+++ b/app/assets/javascripts/group_avatar.js
@@ -1,13 +1,13 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */
(function() {
this.GroupAvatar = (function() {
function GroupAvatar() {
- $('.js-choose-group-avatar-button').bind("click", function() {
+ $('.js-choose-group-avatar-button').on("click", function() {
var form;
form = $(this).closest("form");
return form.find(".js-group-avatar-input").click();
});
- $('.js-group-avatar-input').bind("change", function() {
+ $('.js-group-avatar-input').on("change", function() {
var filename, form;
form = $(this).closest("form");
filename = $(this).val().replace(/^.*[\\\/]/, '');
@@ -16,7 +16,5 @@
}
return GroupAvatar;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/group_label_subscription.js.es6 b/app/assets/javascripts/group_label_subscription.js.es6
index 8e10e424412..15e695e81cf 100644
--- a/app/assets/javascripts/group_label_subscription.js.es6
+++ b/app/assets/javascripts/group_label_subscription.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, padded-blocks, max-len */
+/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, max-len */
(function(global) {
class GroupLabelSubscription {
@@ -50,5 +50,4 @@
}
global.GroupLabelSubscription = GroupLabelSubscription;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index 99700e7562a..a50bc4a9057 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var, camelcase, one-var-declaration-per-line, quotes, object-shorthand, prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var, camelcase, one-var-declaration-per-line, quotes, object-shorthand, prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, max-len */
/* global Api */
(function() {
@@ -67,7 +67,5 @@
};
return GroupsSelect;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
index c7cbf9ca44b..fa85f9a6c86 100644
--- a/app/assets/javascripts/header.js
+++ b/app/assets/javascripts/header.js
@@ -1,10 +1,8 @@
-/* eslint-disable wrap-iife, func-names, space-before-function-paren, padded-blocks, prefer-arrow-callback, no-var, max-len */
+/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, no-var, max-len */
(function() {
-
$(document).on('todo:toggle', function(e, count) {
var $todoPendingCount = $('.todos-pending-count');
$todoPendingCount.text(gl.text.addDelimiter(count));
$todoPendingCount.toggleClass('hidden', count === 0);
});
-
})();
diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js
index fa795be07ed..9390136d3d8 100644
--- a/app/assets/javascripts/importer_status.js
+++ b/app/assets/javascripts/importer_status.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, camelcase, no-var, one-var, one-var-declaration-per-line, prefer-template, quotes, object-shorthand, comma-dangle, no-unused-vars, prefer-arrow-callback, no-else-return, padded-blocks, vars-on-top, no-new, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, camelcase, no-var, one-var, one-var-declaration-per-line, prefer-template, quotes, object-shorthand, comma-dangle, no-unused-vars, prefer-arrow-callback, no-else-return, vars-on-top, no-new, max-len */
(function() {
window.ImporterStatus = (function() {
@@ -68,7 +68,6 @@
};
return ImporterStatus;
-
})();
$(function() {
diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js.es6
index 9c3c96c20ed..f63d700fd65 100644
--- a/app/assets/javascripts/issuable.js.es6
+++ b/app/assets/javascripts/issuable.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, prefer-const, padded-blocks, wrap-iife, max-len */
+/* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */
/* global Issuable */
/* global Turbolinks */
@@ -34,7 +34,6 @@
e.preventDefault();
debouncedExecSearch(e);
});
-
},
initSearchState: function($searchInput) {
const currentSearchVal = $searchInput.val();
@@ -152,7 +151,7 @@
this.issuableBulkActions.setOriginalDropdownData();
if ($checkedIssues.length > 0) {
- let ids = $.map($checkedIssues, function(value) {
+ const ids = $.map($checkedIssues, function(value) {
return $(value).data('id');
});
$updateIssuesIds.val(ids);
@@ -187,5 +186,4 @@
});
}
};
-
})(window);
diff --git a/app/assets/javascripts/issuable/issuable_bundle.js.es6 b/app/assets/javascripts/issuable/issuable_bundle.js.es6
new file mode 100644
index 00000000000..7d0465aa8b4
--- /dev/null
+++ b/app/assets/javascripts/issuable/issuable_bundle.js.es6
@@ -0,0 +1 @@
+//= require ./time_tracking/time_tracking_bundle
diff --git a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6
new file mode 100644
index 00000000000..72433df2818
--- /dev/null
+++ b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6
@@ -0,0 +1,42 @@
+/* global Vue */
+//= require lib/utils/pretty_time
+
+(() => {
+ Vue.component('time-tracking-collapsed-state', {
+ name: 'time-tracking-collapsed-state',
+ props: [
+ 'showComparisonState',
+ 'showSpentOnlyState',
+ 'showEstimateOnlyState',
+ 'showNoTimeTrackingState',
+ 'timeSpentHumanReadable',
+ 'timeEstimateHumanReadable',
+ 'stopwatchSvg',
+ ],
+ methods: {
+ abbreviateTime(timeStr) {
+ return gl.utils.prettyTime.abbreviateTime(timeStr);
+ },
+ },
+ template: `
+ <div class='sidebar-collapsed-icon'>
+ <div v-html='stopwatchSvg'></div>
+ <div class='time-tracking-collapsed-summary'>
+ <div class='compare' v-if='showComparisonState'>
+ <span>{{ abbreviateTime(timeSpentHumanReadable) }} / {{ abbreviateTime(timeEstimateHumanReadable) }}</span>
+ </div>
+ <div class='estimate-only' v-if='showEstimateOnlyState'>
+ <span class='bold'>-- / {{ abbreviateTime(timeEstimateHumanReadable) }}</span>
+ </div>
+ <div class='spend-only' v-if='showSpentOnlyState'>
+ <span class='bold'>{{ abbreviateTime(timeSpentHumanReadable) }} / --</span>
+ </div>
+ <div class='no-tracking' v-if='showNoTimeTrackingState'>
+ <span class='no-value'>None</span>
+ </div>
+ </div>
+ </div>
+ `,
+ });
+})();
+
diff --git a/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es6
new file mode 100644
index 00000000000..6abbd5dd167
--- /dev/null
+++ b/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es6
@@ -0,0 +1,69 @@
+/* global Vue */
+//= require lib/utils/pretty_time
+
+(() => {
+ const prettyTime = gl.utils.prettyTime;
+
+ Vue.component('time-tracking-comparison-pane', {
+ name: 'time-tracking-comparison-pane',
+ props: [
+ 'timeSpent',
+ 'timeEstimate',
+ 'timeSpentHumanReadable',
+ 'timeEstimateHumanReadable',
+ ],
+ computed: {
+ parsedRemaining() {
+ const diffSeconds = this.timeEstimate - this.timeSpent;
+ return prettyTime.parseSeconds(diffSeconds);
+ },
+ timeRemainingHumanReadable() {
+ return prettyTime.stringifyTime(this.parsedRemaining);
+ },
+ timeRemainingTooltip() {
+ const prefix = this.timeRemainingMinutes < 0 ? 'Over by' : 'Time remaining:';
+ return `${prefix} ${this.timeRemainingHumanReadable}`;
+ },
+ /* Diff values for comparison meter */
+ timeRemainingMinutes() {
+ return this.timeEstimate - this.timeSpent;
+ },
+ timeRemainingPercent() {
+ return `${Math.floor((this.timeSpent / this.timeEstimate) * 100)}%`;
+ },
+ timeRemainingStatusClass() {
+ return this.timeEstimate >= this.timeSpent ? 'within_estimate' : 'over_estimate';
+ },
+ /* Parsed time values */
+ parsedEstimate() {
+ return prettyTime.parseSeconds(this.timeEstimate);
+ },
+ parsedSpent() {
+ return prettyTime.parseSeconds(this.timeSpent);
+ },
+ },
+ template: `
+ <div class='time-tracking-comparison-pane'>
+ <div class='compare-meter' data-toggle='tooltip' data-placement='top' role='timeRemainingDisplay'
+ :aria-valuenow='timeRemainingTooltip'
+ :title='timeRemainingTooltip'
+ :data-original-title='timeRemainingTooltip'
+ :class='timeRemainingStatusClass'>
+ <div class='meter-container' role='timeSpentPercent' :aria-valuenow='timeRemainingPercent'>
+ <div :style='{ width: timeRemainingPercent }' class='meter-fill'></div>
+ </div>
+ <div class='compare-display-container'>
+ <div class='compare-display pull-left'>
+ <span class='compare-label'>Spent</span>
+ <span class='compare-value spent'>{{ timeSpentHumanReadable }}</span>
+ </div>
+ <div class='compare-display estimated pull-right'>
+ <span class='compare-label'>Est</span>
+ <span class='compare-value'>{{ timeEstimateHumanReadable }}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ `,
+ });
+})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es6
new file mode 100644
index 00000000000..309e9f2f9ef
--- /dev/null
+++ b/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es6
@@ -0,0 +1,13 @@
+/* global Vue */
+(() => {
+ Vue.component('time-tracking-estimate-only-pane', {
+ name: 'time-tracking-estimate-only-pane',
+ props: ['timeEstimateHumanReadable'],
+ template: `
+ <div class='time-tracking-estimate-only-pane'>
+ <span class='bold'>Estimated:</span>
+ {{ timeEstimateHumanReadable }}
+ </div>
+ `,
+ });
+})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/help_state.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/help_state.js.es6
new file mode 100644
index 00000000000..d7ced6d7151
--- /dev/null
+++ b/app/assets/javascripts/issuable/time_tracking/components/help_state.js.es6
@@ -0,0 +1,24 @@
+/* global Vue */
+(() => {
+ Vue.component('time-tracking-help-state', {
+ name: 'time-tracking-help-state',
+ props: ['docsUrl'],
+ template: `
+ <div class='time-tracking-help-state'>
+ <div class='time-tracking-info'>
+ <h4>Track time with slash commands</h4>
+ <p>Slash commands can be used in the issues description and comment boxes.</p>
+ <p>
+ <code>/estimate</code>
+ will update the estimated time with the latest command.
+ </p>
+ <p>
+ <code>/spend</code>
+ will update the sum of the time spent.
+ </p>
+ <a class='btn btn-default learn-more-button' :href='docsUrl'>Learn more</a>
+ </div>
+ </div>
+ `,
+ });
+})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es6
new file mode 100644
index 00000000000..1d2ca643b5b
--- /dev/null
+++ b/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es6
@@ -0,0 +1,11 @@
+/* global Vue */
+(() => {
+ Vue.component('time-tracking-no-tracking-pane', {
+ name: 'time-tracking-no-tracking-pane',
+ template: `
+ <div class='time-tracking-no-tracking-pane'>
+ <span class='no-value'>No estimate or time spent</span>
+ </div>
+ `,
+ });
+})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es6
new file mode 100644
index 00000000000..ed283fec3c3
--- /dev/null
+++ b/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es6
@@ -0,0 +1,13 @@
+/* global Vue */
+(() => {
+ Vue.component('time-tracking-spent-only-pane', {
+ name: 'time-tracking-spent-only-pane',
+ props: ['timeSpentHumanReadable'],
+ template: `
+ <div class='time-tracking-spend-only-pane'>
+ <span class='bold'>Spent:</span>
+ {{ timeSpentHumanReadable }}
+ </div>
+ `,
+ });
+})();
diff --git a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6
new file mode 100644
index 00000000000..26563a7713b
--- /dev/null
+++ b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6
@@ -0,0 +1,118 @@
+/* global Vue */
+//= require ./help_state
+//= require ./collapsed_state
+//= require ./spent_only_pane
+//= require ./no_tracking_pane
+//= require ./estimate_only_pane
+//= require ./comparison_pane
+
+(() => {
+ Vue.component('issuable-time-tracker', {
+ name: 'issuable-time-tracker',
+ props: [
+ 'time_estimate',
+ 'time_spent',
+ 'human_time_estimate',
+ 'human_time_spent',
+ 'stopwatchSvg',
+ 'docsUrl',
+ ],
+ data() {
+ return {
+ showHelp: false,
+ };
+ },
+ computed: {
+ timeSpent() {
+ return this.time_spent;
+ },
+ timeEstimate() {
+ return this.time_estimate;
+ },
+ timeEstimateHumanReadable() {
+ return this.human_time_estimate;
+ },
+ timeSpentHumanReadable() {
+ return this.human_time_spent;
+ },
+ hasTimeSpent() {
+ return !!this.timeSpent;
+ },
+ hasTimeEstimate() {
+ return !!this.timeEstimate;
+ },
+ showComparisonState() {
+ return this.hasTimeEstimate && this.hasTimeSpent;
+ },
+ showEstimateOnlyState() {
+ return this.hasTimeEstimate && !this.hasTimeSpent;
+ },
+ showSpentOnlyState() {
+ return this.hasTimeSpent && !this.hasTimeEstimate;
+ },
+ showNoTimeTrackingState() {
+ return !this.hasTimeEstimate && !this.hasTimeSpent;
+ },
+ showHelpState() {
+ return !!this.showHelp;
+ },
+ },
+ methods: {
+ toggleHelpState(show) {
+ this.showHelp = show;
+ },
+ },
+ template: `
+ <div class='time_tracker time-tracking-component-wrap' v-cloak>
+ <time-tracking-collapsed-state
+ :show-comparison-state='showComparisonState'
+ :show-help-state='showHelpState'
+ :show-spent-only-state='showSpentOnlyState'
+ :show-estimate-only-state='showEstimateOnlyState'
+ :time-spent-human-readable='timeSpentHumanReadable'
+ :time-estimate-human-readable='timeEstimateHumanReadable'
+ :stopwatch-svg='stopwatchSvg'>
+ </time-tracking-collapsed-state>
+ <div class='title hide-collapsed'>
+ Time tracking
+ <div class='help-button pull-right'
+ v-if='!showHelpState'
+ @click='toggleHelpState(true)'>
+ <i class='fa fa-question-circle'></i>
+ </div>
+ <div class='close-help-button pull-right'
+ v-if='showHelpState'
+ @click='toggleHelpState(false)'>
+ <i class='fa fa-close'></i>
+ </div>
+ </div>
+ <div class='time-tracking-content hide-collapsed'>
+ <time-tracking-estimate-only-pane
+ v-if='showEstimateOnlyState'
+ :time-estimate-human-readable='timeEstimateHumanReadable'>
+ </time-tracking-estimate-only-pane>
+ <time-tracking-spent-only-pane
+ v-if='showSpentOnlyState'
+ :time-spent-human-readable='timeSpentHumanReadable'>
+ </time-tracking-spent-only-pane>
+ <time-tracking-no-tracking-pane
+ v-if='showNoTimeTrackingState'>
+ </time-tracking-no-tracking-pane>
+ <time-tracking-comparison-pane
+ v-if='showComparisonState'
+ :time-estimate='timeEstimate'
+ :time-spent='timeSpent'
+ :time-spent-human-readable='timeSpentHumanReadable'
+ :time-estimate-human-readable='timeEstimateHumanReadable'>
+ </time-tracking-comparison-pane>
+ <transition name='help-state-toggle'>
+ <time-tracking-help-state
+ v-if='showHelpState'
+ :docs-url='docsUrl'>
+ </time-tracking-help-state>
+ </transition>
+ </div>
+ </div>
+ `,
+ });
+})();
diff --git a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6
new file mode 100644
index 00000000000..0b8da2b1f4f
--- /dev/null
+++ b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6
@@ -0,0 +1,61 @@
+/* global Vue */
+//= require ./components/time_tracker
+//= require smart_interval
+//= require subbable_resource
+
+(() => {
+ /* This Vue instance represents what will become the parent instance for the
+ * sidebar. It will be responsible for managing `issuable` state and propagating
+ * changes to sidebar components. We will want to create a separate service to
+ * interface with the server at that point.
+ */
+
+ class IssuableTimeTracking {
+ constructor(issuableJSON) {
+ const parsedIssuable = JSON.parse(issuableJSON);
+ return this.initComponent(parsedIssuable);
+ }
+
+ initComponent(parsedIssuable) {
+ this.parentInstance = new Vue({
+ el: '#issuable-time-tracker',
+ data: {
+ issuable: parsedIssuable,
+ },
+ methods: {
+ fetchIssuable() {
+ return gl.IssuableResource.get.call(gl.IssuableResource, {
+ type: 'GET',
+ url: gl.IssuableResource.endpoint,
+ });
+ },
+ updateState(data) {
+ this.issuable = data;
+ },
+ subscribeToUpdates() {
+ gl.IssuableResource.subscribe(data => this.updateState(data));
+ },
+ listenForSlashCommands() {
+ $(document).on('ajax:success', '.gfm-form', (e, data) => {
+ const subscribedCommands = ['spend_time', 'time_estimate'];
+ const changedCommands = data.commands_changes;
+
+ if (changedCommands && _.intersection(subscribedCommands, changedCommands).length) {
+ this.fetchIssuable();
+ }
+ });
+ },
+ },
+ created() {
+ this.fetchIssuable();
+ },
+ mounted() {
+ this.subscribeToUpdates();
+ this.listenForSlashCommands();
+ },
+ });
+ }
+ }
+
+ gl.IssuableTimeTracking = IssuableTimeTracking;
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index 4aaad111082..c77fbb6a1c7 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -1,5 +1,7 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
/* global UsersSelect */
+/* global Cookies */
+/* global bp */
(function() {
this.IssuableContext = (function() {
@@ -37,6 +39,13 @@
}, 0);
}
});
+ window.addEventListener('beforeunload', function() {
+ // collapsed_gutter cookie hides the sidebar
+ var bpBreakpoint = bp.getBreakpointSize();
+ if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') {
+ Cookies.set('collapsed_gutter', true);
+ }
+ });
$(".right-sidebar").niceScroll();
}
@@ -66,7 +75,5 @@
};
return IssuableContext;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 1c4086517fe..293b856dc4d 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -1,11 +1,11 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, radix, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-useless-escape, no-new, quotes, object-shorthand, no-unused-vars, comma-dangle, no-alert, consistent-return, no-else-return, prefer-template, one-var, one-var-declaration-per-line, curly, max-len */
/* global GitLab */
/* global UsersSelect */
/* global ZenMode */
/* global Autosave */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.IssuableForm = (function() {
IssuableForm.prototype.issueMoveConfirmMsg = 'Are you sure you want to move this issue to another project?';
@@ -51,7 +51,7 @@
IssuableForm.prototype.handleSubmit = function() {
var fieldId = (this.issueMoveField != null) ? this.issueMoveField.val() : null;
- if ((parseInt(fieldId) || 0) > 0) {
+ if ((parseInt(fieldId, 10) || 0) > 0) {
if (!confirm(this.issueMoveConfirmMsg)) {
return false;
}
@@ -150,7 +150,5 @@
};
return IssuableForm;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js
index 61e8531153b..081b0d8b0d7 100644
--- a/app/assets/javascripts/issue.js
+++ b/app/assets/javascripts/issue.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
/* global Flash */
/*= require flash */
@@ -6,7 +6,7 @@
/*= require task_list */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.Issue = (function() {
function Issue() {
@@ -151,7 +151,5 @@
};
return Issue;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js
index b39d8274e13..1d6eff11403 100644
--- a/app/assets/javascripts/issue_status_select.js
+++ b/app/assets/javascripts/issue_status_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
(function() {
this.IssueStatusSelect = (function() {
function IssueStatusSelect() {
@@ -30,7 +30,5 @@
}
return IssueStatusSelect;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/issues_bulk_assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6
index 52fd5d71b18..e0ebd36a65c 100644
--- a/app/assets/javascripts/issues_bulk_assignment.js.es6
+++ b/app/assets/javascripts/issues_bulk_assignment.js.es6
@@ -1,9 +1,8 @@
-/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, radix, max-len, padded-blocks, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */
+/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, space-before-function-paren, prefer-arrow-callback, max-len, no-unused-expressions, no-sequences, no-underscore-dangle, no-unused-vars, no-param-reassign */
/* global Issuable */
/* global Flash */
((global) => {
-
class IssuableBulkActions {
constructor({ container, form, issues, prefixId } = {}) {
this.prefixId = prefixId || 'issue_';
@@ -62,7 +61,6 @@
return labels;
}
-
/**
* Will return only labels that were marked previously and the user has unmarked
* @return {Array} Label IDs
@@ -81,7 +79,6 @@
return result;
}
-
/**
* Simple form serialization, it will return just what we need
* Returns key/value pairs from form data
@@ -163,5 +160,4 @@
}
global.IssuableBulkActions = IssuableBulkActions;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6
index 33c5e35324d..8f48b1f57ce 100644
--- a/app/assets/javascripts/label_manager.js.es6
+++ b/app/assets/javascripts/label_manager.js.es6
@@ -1,8 +1,7 @@
-/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, padded-blocks, max-len */
+/* eslint-disable comma-dangle, class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, consistent-return, func-names, space-before-function-paren, max-len */
/* global Flash */
((global) => {
-
class LabelManager {
constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority');
@@ -104,5 +103,4 @@
}
gl.LabelManager = LabelManager;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js
index 10de13c9a8a..40ad6fc348e 100644
--- a/app/assets/javascripts/labels.js
+++ b/app/assets/javascripts/labels.js
@@ -1,6 +1,6 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, max-len */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.Labels = (function() {
function Labels() {
@@ -42,7 +42,5 @@
};
return Labels;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index ec2fc87bece..70dc0d06b7b 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks */
+/* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread */
/* global Issuable */
/* global ListLabel */
@@ -333,10 +333,14 @@
if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) {
$dropdown.parent()
.find('.dropdown-clear-active')
- .removeClass('is-active')
+ .removeClass('is-active');
}
- if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
+ if ($dropdown.hasClass('js-issuable-form-dropdown')) {
+ return;
+ }
+
+ if ($dropdown.hasClass('js-filter-bulk-update')) {
_this.enableBulkLabelDropdown();
_this.setDropdownData($dropdown, isMarking, this.id(label));
return;
@@ -484,5 +488,4 @@
return LabelsSelect;
})();
-
}).call(this);
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 2b700539c2b..1c0ea317c1a 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -1,4 +1,5 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, indent, vars-on-top, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, no-unused-vars, one-var, one-var-declaration-per-line, vars-on-top, max-len */
+
(function() {
var hideEndFade;
@@ -27,10 +28,10 @@
});
$scrollingTabs.each(function () {
- var $this = $(this),
- scrollingTabWidth = $this.width(),
- $active = $this.find('.active'),
- activeWidth = $active.width();
+ var $this = $(this);
+ var scrollingTabWidth = $this.width();
+ var $active = $this.find('.active');
+ var activeWidth = $active.width();
if ($active.length) {
var offset = $active.offset().left + activeWidth;
@@ -43,5 +44,4 @@
}
});
});
-
}).call(this);
diff --git a/app/assets/javascripts/lib/ace.js b/app/assets/javascripts/lib/ace.js
index 4cdf99cae72..9cdc0309503 100644
--- a/app/assets/javascripts/lib/ace.js
+++ b/app/assets/javascripts/lib/ace.js
@@ -1,2 +1,3 @@
-/*= require ace-rails-ap */
+/*= require ace/ace */
/*= require ace/ext-searchbox */
+/*= require ./ace/ace_config_paths */
diff --git a/app/assets/javascripts/lib/ace/ace_config_paths.js.erb b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb
new file mode 100644
index 00000000000..25e623f0fdc
--- /dev/null
+++ b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb
@@ -0,0 +1,25 @@
+<%
+ace_gem_path = Bundler.rubygems.find_name('ace-rails-ap').first.full_gem_path
+ace_workers = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/worker-*.js'].sort.map do |file|
+ File.basename(file, '.js').sub(/^worker-/, '')
+end
+ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.map do |file|
+ File.basename(file, '.js').sub(/^mode-/, '')
+end
+%>
+
+(function() {
+ window.gon = window.gon || {};
+ var basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace';
+ ace.config.set('basePath', basePath);
+
+ // configure paths for all worker modules
+<% ace_workers.each do |worker| %>
+ ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/worker-<%= worker %>.js');
+<% end %>
+
+ // configure paths for all mode modules
+<% ace_modes.each do |mode| %>
+ ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/mode-<%= mode %>.js');
+<% end %>
+})();
diff --git a/app/assets/javascripts/lib/utils/animate.js b/app/assets/javascripts/lib/utils/animate.js
index 83957af94f3..ce090a2e4fd 100644
--- a/app/assets/javascripts/lib/utils/animate.js
+++ b/app/assets/javascripts/lib/utils/animate.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, no-void, prefer-template, no-var, new-cap, prefer-arrow-callback, consistent-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, no-void, prefer-template, no-var, new-cap, prefer-arrow-callback, consistent-return, max-len */
(function() {
(function(w) {
if (w.gl == null) {
@@ -46,5 +46,4 @@
return dfd.promise();
};
})(window);
-
}).call(this);
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js.es6
index 0a0e73e0ccc..51993bb3420 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len, prefer-template */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, max-len, prefer-template */
(function() {
(function(w) {
var base;
@@ -106,8 +106,9 @@
);
};
- gl.utils.getPagePath = function() {
- return $('body').data('page').split(':')[0];
+ gl.utils.getPagePath = function(index) {
+ index = index || 0;
+ return $('body').data('page').split(':')[index];
};
gl.utils.parseUrl = function (url) {
@@ -123,10 +124,110 @@
return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname;
};
+ gl.utils.getUrlParamsArray = function () {
+ // We can trust that each param has one & since values containing & will be encoded
+ // Remove the first character of search as it is always ?
+ return window.location.search.slice(1).split('&');
+ };
+
gl.utils.isMetaKey = function(e) {
return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
};
- })(window);
+ gl.utils.scrollToElement = function($el) {
+ var top = $el.offset().top;
+ gl.navBarHeight = gl.navBarHeight || $('.navbar-gitlab').height();
+ gl.navLinksHeight = gl.navLinksHeight || $('.nav-links').height();
+ gl.mrTabsHeight = gl.mrTabsHeight || $('.merge-request-tabs').height();
+
+ return $('body, html').animate({
+ scrollTop: top - (gl.navBarHeight + gl.navLinksHeight + gl.mrTabsHeight)
+ }, 200);
+ };
+
+ /**
+ this will take in the `name` of the param you want to parse in the url
+ if the name does not exist this function will return `null`
+ otherwise it will return the value of the param key provided
+ */
+ w.gl.utils.getParameterByName = (name) => {
+ const url = window.location.href;
+ name = name.replace(/[[\]]/g, '\\$&');
+ const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`);
+ const results = regex.exec(url);
+ if (!results) return null;
+ if (!results[2]) return '';
+ return decodeURIComponent(results[2].replace(/\+/g, ' '));
+ };
+
+ w.gl.utils.getSelectedFragment = () => {
+ const selection = window.getSelection();
+ const documentFragment = selection.getRangeAt(0).cloneContents();
+ if (documentFragment.textContent.length === 0) return null;
+
+ return documentFragment;
+ };
+
+ w.gl.utils.insertText = (target, text) => {
+ // Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas
+
+ const selectionStart = target.selectionStart;
+ const selectionEnd = target.selectionEnd;
+ const value = target.value;
+
+ const textBefore = value.substring(0, selectionStart);
+ const textAfter = value.substring(selectionEnd, value.length);
+ const newText = textBefore + text + textAfter;
+
+ target.value = newText;
+ target.selectionStart = target.selectionEnd = selectionStart + text.length;
+
+ // Trigger autosave
+ $(target).trigger('input');
+ // Trigger autosize
+ var event = document.createEvent('Event');
+ event.initEvent('autosize:update', true, false);
+ target.dispatchEvent(event);
+ };
+
+ w.gl.utils.nodeMatchesSelector = (node, selector) => {
+ const matches = Element.prototype.matches ||
+ Element.prototype.matchesSelector ||
+ Element.prototype.mozMatchesSelector ||
+ Element.prototype.msMatchesSelector ||
+ Element.prototype.oMatchesSelector ||
+ Element.prototype.webkitMatchesSelector;
+
+ if (matches) {
+ return matches.call(node, selector);
+ }
+
+ // IE11 doesn't support `node.matches(selector)`
+
+ let parentNode = node.parentNode;
+ if (!parentNode) {
+ parentNode = document.createElement('div');
+ node = node.cloneNode(true);
+ parentNode.appendChild(node);
+ }
+
+ const matchingNodes = parentNode.querySelectorAll(selector);
+ return Array.prototype.indexOf.call(matchingNodes, node) !== -1;
+ };
+
+ /**
+ this will take in the headers from an API response and normalize them
+ this way we don't run into production issues when nginx gives us lowercased header keys
+ */
+ w.gl.utils.normalizeHeaders = (headers) => {
+ const upperCaseHeaders = {};
+
+ Object.keys(headers).forEach((e) => {
+ upperCaseHeaders[e.toUpperCase()] = headers[e];
+ });
+
+ return upperCaseHeaders;
+ };
+ })(window);
}).call(this);
diff --git a/app/assets/javascripts/lib/utils/custom_event_polyfill.js.es6 b/app/assets/javascripts/lib/utils/custom_event_polyfill.js.es6
deleted file mode 100644
index 5ae978010c9..00000000000
--- a/app/assets/javascripts/lib/utils/custom_event_polyfill.js.es6
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
- * CustomEvent support for IE
- */
-if (typeof window.CustomEvent !== 'function') {
- window.CustomEvent = function CustomEvent(e, params) {
- const options = params || { bubbles: false, cancelable: false, detail: undefined };
- const evt = document.createEvent('CustomEvent');
- evt.initCustomEvent(e, options.bubbles, options.cancelable, options.detail);
- return evt;
- };
- window.CustomEvent.prototype = window.Event.prototype;
-}
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index e8e502694d6..3ed8bfd5651 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */
/* global timeago */
/* global dateFormat */
@@ -97,7 +97,5 @@
return Math.floor((date2 - date1) / millisecondsPerDay);
};
-
})(window);
-
}).call(this);
diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js
index 3c9ad0e67c8..6d5979603b9 100644
--- a/app/assets/javascripts/lib/utils/notify.js
+++ b/app/assets/javascripts/lib/utils/notify.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, max-len */
(function() {
(function(w) {
@@ -44,5 +44,4 @@
w.notify = notifyMe;
return w.notifyPermissions = notifyPermissions;
})(window);
-
}).call(this);
diff --git a/app/assets/javascripts/lib/utils/pretty_time.js.es6 b/app/assets/javascripts/lib/utils/pretty_time.js.es6
index ccaf447eb0b..ae397212e55 100644
--- a/app/assets/javascripts/lib/utils/pretty_time.js.es6
+++ b/app/assets/javascripts/lib/utils/pretty_time.js.es6
@@ -4,13 +4,13 @@
* stringifyTime condensed or non-condensed, abbreviateTimelengths)
* */
- class PrettyTime {
-
+ const utils = window.gl.utils = gl.utils || {};
+ const prettyTime = utils.prettyTime = {
/*
* Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # }
* Seconds can be negative or positive, zero or non-zero.
*/
- static parseSeconds(seconds) {
+ parseSeconds(seconds) {
const DAYS_PER_WEEK = 5;
const HOURS_PER_DAY = 8;
const MINUTES_PER_HOUR = 60;
@@ -24,7 +24,7 @@
minutes: 1,
};
- let unorderedMinutes = PrettyTime.secondsToMinutes(seconds);
+ let unorderedMinutes = prettyTime.secondsToMinutes(seconds);
return _.mapObject(timePeriodConstraints, (minutesPerPeriod) => {
const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod);
@@ -33,35 +33,33 @@
return periodCount;
});
- }
+ },
/*
* Accepts a timeObject and returns a condensed string representation of it
* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included.
*/
- static stringifyTime(timeObject) {
+ stringifyTime(timeObject) {
const reducedTime = _.reduce(timeObject, (memo, unitValue, unitName) => {
const isNonZero = !!unitValue;
return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo;
}, '').trim();
return reducedTime.length ? reducedTime : '0m';
- }
+ },
/*
* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns
* the first non-zero unit/value pair.
*/
- static abbreviateTime(timeStr) {
+ abbreviateTime(timeStr) {
return timeStr.split(' ')
.filter(unitStr => unitStr.charAt(0) !== '0')[0];
- }
+ },
- static secondsToMinutes(seconds) {
+ secondsToMinutes(seconds) {
return Math.abs(seconds / 60);
- }
- }
-
- gl.PrettyTime = PrettyTime;
+ },
+ };
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index ac44b81ee22..6bb575059b7 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -1,4 +1,5 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, semi, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len */
+
(function() {
(function(w) {
var base;
@@ -10,13 +11,28 @@
}
gl.text.addDelimiter = function(text) {
return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text;
- }
+ };
gl.text.randomString = function() {
return Math.random().toString(36).substring(7);
};
gl.text.replaceRange = function(s, start, end, substitute) {
return s.substring(0, start) + substitute + s.substring(end);
};
+ gl.text.getTextWidth = function(text, font) {
+ /**
+ * Uses canvas.measureText to compute and return the width of the given text of given font in pixels.
+ *
+ * @param {String} text The text to be rendered.
+ * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana").
+ *
+ * @see http://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
+ */
+ // re-use canvas object for better performance
+ var canvas = gl.text.getTextWidth.canvas || (gl.text.getTextWidth.canvas = document.createElement('canvas'));
+ var context = canvas.getContext('2d');
+ context.font = font;
+ return context.measureText(text).width;
+ };
gl.text.selectedText = function(text, textarea) {
return text.substring(textarea.selectionStart, textarea.selectionEnd);
};
@@ -44,9 +60,25 @@
}
};
gl.text.insertText = function(textArea, text, tag, blockTag, selected, wrap) {
- var insertText, inserted, selectedSplit, startChar;
+ var insertText, inserted, selectedSplit, startChar, removedLastNewLine, removedFirstNewLine;
+ removedLastNewLine = false;
+ removedFirstNewLine = false;
+
+ // Remove the first newline
+ if (selected.indexOf('\n') === 0) {
+ removedFirstNewLine = true;
+ selected = selected.replace(/\n+/, '');
+ }
+
+ // Remove the last newline
+ if (textArea.selectionEnd - textArea.selectionStart > selected.replace(/\n$/, '').length) {
+ removedLastNewLine = true;
+ selected = selected.replace(/\n$/, '');
+ }
+
selectedSplit = selected.split('\n');
startChar = !wrap && textArea.selectionStart > 0 ? '\n' : '';
+
if (selectedSplit.length > 1 && (!wrap || (blockTag != null))) {
if (blockTag != null) {
insertText = this.blockTagText(text, textArea, blockTag, selected);
@@ -62,6 +94,15 @@
} else {
insertText = "" + startChar + tag + selected + (wrap ? tag : ' ');
}
+
+ if (removedFirstNewLine) {
+ insertText = '\n' + insertText;
+ }
+
+ if (removedLastNewLine) {
+ insertText += '\n';
+ }
+
if (document.queryCommandSupported('insertText')) {
inserted = document.execCommand('insertText', false, insertText);
}
@@ -74,9 +115,9 @@
document.execCommand("ms-endUndoUnit");
} catch (error) {}
}
- return this.moveCursor(textArea, tag, wrap);
+ return this.moveCursor(textArea, tag, wrap, removedLastNewLine);
};
- gl.text.moveCursor = function(textArea, tag, wrapped) {
+ gl.text.moveCursor = function(textArea, tag, wrapped, removedLastNewLine) {
var pos;
if (!textArea.setSelectionRange) {
return;
@@ -87,6 +128,11 @@
} else {
pos = textArea.selectionStart;
}
+
+ if (removedLastNewLine) {
+ pos -= 1;
+ }
+
return textArea.setSelectionRange(pos, pos);
}
};
@@ -114,10 +160,9 @@
};
gl.text.humanize = function(string) {
return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
- }
+ };
return gl.text.truncate = function(string, maxLength) {
return string.substr(0, (maxLength - 3)) + '...';
};
})(window);
-
}).call(this);
diff --git a/app/assets/javascripts/lib/utils/type_utility.js b/app/assets/javascripts/lib/utils/type_utility.js
index 961859dfb5b..6d813d61601 100644
--- a/app/assets/javascripts/lib/utils/type_utility.js
+++ b/app/assets/javascripts/lib/utils/type_utility.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, no-return-assign, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, no-return-assign, max-len */
(function() {
(function(w) {
var base;
@@ -12,5 +12,4 @@
return (obj != null) && (obj.constructor === Object);
};
})(window);
-
}).call(this);
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 6872186cd7f..8e15bf0735c 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, no-plusplus, guard-for-in, no-restricted-syntax, prefer-template, quotes, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, one-var, one-var-declaration-per-line, no-void, guard-for-in, no-restricted-syntax, prefer-template, quotes, max-len */
(function() {
(function(w) {
var base;
@@ -22,7 +22,7 @@
if (sParameterName[0] === sParam) {
values.push(sParameterName[1].replace(/\+/g, ' '));
}
- i++;
+ i += 1;
}
return values;
};
@@ -57,7 +57,7 @@
return ((function() {
var j, len, results;
results = [];
- for (j = 0, len = urlVariables.length; j < len; j++) {
+ for (j = 0, len = urlVariables.length; j < len; j += 1) {
variables = urlVariables[j];
if (variables.indexOf(param) === -1) {
results.push(variables);
@@ -77,5 +77,4 @@
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
};
})(window);
-
}).call(this);
diff --git a/app/assets/javascripts/lib/vue_resource.js.es6 b/app/assets/javascripts/lib/vue_resource.js.es6
new file mode 100644
index 00000000000..eff1dcabfa2
--- /dev/null
+++ b/app/assets/javascripts/lib/vue_resource.js.es6
@@ -0,0 +1,2 @@
+//= require vue
+//= require vue-resource
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js
index 9af89b79f84..2f147704c22 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/line_highlighter.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-underscore-dangle, no-param-reassign, prefer-template, quotes, comma-dangle, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, spaced-comment, radix, no-else-return, max-len, no-plusplus, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-underscore-dangle, no-param-reassign, prefer-template, quotes, comma-dangle, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-else-return, max-len */
// LineHighlighter
//
@@ -31,7 +31,7 @@
// </div>
//
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.LineHighlighter = (function() {
// CSS class applied to highlighted lines
@@ -74,8 +74,9 @@
// If not done this way, the line number anchor will sometimes keep its
// active state even when the event is cancelled, resulting in an ugly border
// around the link and/or a persisted underline text decoration.
- return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
- return event.preventDefault();
+ $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
+ event.preventDefault();
+ event.stopPropagation();
});
};
@@ -119,11 +120,11 @@
// Returns an Array
LineHighlighter.prototype.hashToRange = function(hash) {
var first, last, matches;
- //?L(\d+)(?:-(\d+))?$/)
+ // ?L(\d+)(?:-(\d+))?$/)
matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
if (matches && matches.length) {
- first = parseInt(matches[1]);
- last = matches[2] ? parseInt(matches[2]) : null;
+ first = parseInt(matches[1], 10);
+ last = matches[2] ? parseInt(matches[2], 10) : null;
return [first, last];
} else {
return [null, null];
@@ -144,7 +145,7 @@
var i, lineNumber, ref, ref1, results;
if (range[1]) {
results = [];
- for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? ++i : --i) {
+ for (lineNumber = i = ref = range[0], ref1 = range[1]; ref <= ref1 ? i <= ref1 : i >= ref1; lineNumber = ref <= ref1 ? (i += 1) : (i -= 1)) {
results.push(this.highlightLine(lineNumber));
}
return results;
@@ -178,7 +179,5 @@
};
return LineHighlighter;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js
index 0ae6df311bb..ea9bfb4860a 100644
--- a/app/assets/javascripts/logo.js
+++ b/app/assets/javascripts/logo.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback */
/* global Turbolinks */
(function() {
@@ -11,5 +11,4 @@
$(document).on('page:change', function() {
$('.tanuki-logo').removeClass('animate');
});
-
}).call(this);
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js.es6
index 7741cd29793..bf6c0ec2798 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js.es6
@@ -1,30 +1,29 @@
-/* eslint-disable func-names, space-before-function-paren, vars-on-top, no-var, object-shorthand, comma-dangle, max-len */
-(function() {
+(() => {
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
// `js-clear-input` element, then show that element when there is a value in the
// datepicker, and make clicking on that element clear the field.
//
- gl.MemberExpirationDate = function() {
+ window.gl = window.gl || {};
+ gl.MemberExpirationDate = (selector = '.js-access-expiration-date') => {
function toggleClearInput() {
$(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== '');
}
-
- var inputs = $('.js-access-expiration-date');
+ const inputs = $(selector);
inputs.datepicker({
dateFormat: 'yy-mm-dd',
minDate: 1,
- onSelect: function () {
+ onSelect: function onSelect() {
$(this).trigger('change');
toggleClearInput.call(this);
- }
+ },
});
- inputs.next('.js-clear-input').on('click', function(event) {
+ inputs.next('.js-clear-input').on('click', function clicked(event) {
event.preventDefault();
- var input = $(this).closest('.clearable-input').find('.js-access-expiration-date');
+ const input = $(this).closest('.clearable-input').find(selector);
input.datepicker('setDate', null)
.trigger('change');
toggleClearInput.call(input);
diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
index f95b079c972..c7e78fed8fe 100644
--- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
+++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
@@ -1,10 +1,9 @@
-/* eslint-disable comma-dangle, quote-props, no-useless-computed-key, object-shorthand, prefer-const, no-new, padded-blocks, no-param-reassign, semi, max-len */
+/* eslint-disable comma-dangle, quote-props, no-useless-computed-key, object-shorthand, no-new, no-param-reassign, max-len */
/* global Vue */
/* global ace */
/* global Flash */
((global) => {
-
global.mergeConflicts = global.mergeConflicts || {};
global.mergeConflicts.diffFileEditor = Vue.extend({
@@ -19,7 +18,7 @@
loading: false,
fileLoaded: false,
originalContent: '',
- }
+ };
},
computed: {
classObject() {
@@ -51,8 +50,8 @@
$.get(this.file.content_path)
.done((file) => {
- let content = this.$el.querySelector('pre');
- let fileContent = document.createTextNode(file.content);
+ const content = this.$el.querySelector('pre');
+ const fileContent = document.createTextNode(file.content);
content.textContent = fileContent.textContent;
@@ -94,5 +93,4 @@
}
}
});
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6
index 74544b7d0c7..240c8f98932 100644
--- a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6
+++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6
@@ -1,8 +1,7 @@
-/* eslint-disable padded-blocks, no-param-reassign, comma-dangle */
+/* eslint-disable no-param-reassign, comma-dangle */
/* global Vue */
((global) => {
-
global.mergeConflicts = global.mergeConflicts || {};
global.mergeConflicts.inlineConflictLines = Vue.extend({
@@ -11,5 +10,4 @@
},
mixins: [global.mergeConflicts.utils, global.mergeConflicts.actions],
});
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6
index 78c00c31c16..97753c50b60 100644
--- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6
@@ -1,8 +1,7 @@
-/* eslint-disable padded-blocks, no-param-reassign, comma-dangle */
+/* eslint-disable no-param-reassign, comma-dangle */
/* global Vue */
((global) => {
-
global.mergeConflicts = global.mergeConflicts || {};
global.mergeConflicts.parallelConflictLines = Vue.extend({
@@ -26,5 +25,4 @@
</table>
`,
});
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6
index 8df3170edac..c012b77e0bf 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-param-reassign, comma-dangle, no-extra-semi, padded-blocks */
+/* eslint-disable no-param-reassign, comma-dangle */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
@@ -25,8 +25,7 @@
method: 'POST'
});
}
- };
+ }
global.mergeConflicts.mergeConflictsService = mergeConflictsService;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
index 53b44007510..74587df22c5 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable comma-dangle, object-shorthand, no-dupe-keys, no-param-reassign, no-plusplus, camelcase, prefer-const, no-nested-ternary, no-continue, semi, func-call-spacing, no-spaced-func, padded-blocks, max-len */
+/* eslint-disable comma-dangle, object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue, max-len */
/* global Cookies */
/* global Vue */
@@ -40,7 +40,6 @@
commitMessage: data.commit_message,
sourceBranch: data.source_branch,
targetBranch: data.target_branch,
- commitMessage: data.commit_message,
shortCommitSha: data.commit_sha.slice(0, 7),
};
},
@@ -89,7 +88,7 @@
this.decorateLineForInlineView(line, id, conflict);
file.inlineLines.push(line);
- })
+ });
if (conflict) {
file.inlineLines.push(this.getOriginHeaderLine(id));
@@ -121,7 +120,7 @@
} else {
const lineType = type || 'context';
- linesObj.left.push (this.getLineForParallelView(line, id, lineType));
+ linesObj.left.push(this.getLineForParallelView(line, id, lineType));
linesObj.right.push(this.getLineForParallelView(line, id, lineType, true));
}
});
@@ -129,7 +128,7 @@
this.checkLineLengths(linesObj);
});
- for (let i = 0, len = linesObj.left.length; i < len; i++) {
+ for (let i = 0, len = linesObj.left.length; i < len; i += 1) {
file.parallelLines.push([
linesObj.right[i],
linesObj.left[i]
@@ -162,11 +161,11 @@
if (file.type === CONFLICT_TYPES.TEXT) {
file.sections.forEach((section) => {
if (section.conflict) {
- count++;
+ count += 1;
}
});
} else {
- count++;
+ count += 1;
}
});
@@ -252,17 +251,17 @@
},
checkLineLengths(linesObj) {
- let { left, right } = linesObj;
+ const { left, right } = linesObj;
if (left.length !== right.length) {
if (left.length > right.length) {
const diff = left.length - right.length;
- for (let i = 0; i < diff; i++) {
+ for (let i = 0; i < diff; i += 1) {
right.push({ lineType: 'emptyLine', richText: '' });
}
} else {
const diff = right.length - left.length;
- for (let i = 0; i < diff; i++) {
+ for (let i = 0; i < diff; i += 1) {
left.push({ lineType: 'emptyLine', richText: '' });
}
}
@@ -316,32 +315,31 @@
const hasCommitMessage = $.trim(this.state.conflictsData.commitMessage).length;
let unresolved = 0;
- for (let i = 0, l = files.length; i < l; i++) {
- let file = files[i];
+ for (let i = 0, l = files.length; i < l; i += 1) {
+ const file = files[i];
if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
let numberConflicts = 0;
- let resolvedConflicts = Object.keys(file.resolutionData).length
+ const resolvedConflicts = Object.keys(file.resolutionData).length;
// We only check for conflicts type 'text'
// since conflicts `text_editor` can´t be resolved in interactive mode
if (file.type === CONFLICT_TYPES.TEXT) {
- for (let j = 0, k = file.sections.length; j < k; j++) {
+ for (let j = 0, k = file.sections.length; j < k; j += 1) {
if (file.sections[j].conflict) {
- numberConflicts++;
+ numberConflicts += 1;
}
}
if (resolvedConflicts !== numberConflicts) {
- unresolved++;
+ unresolved += 1;
}
}
} else if (file.resolveMode === EDIT_RESOLVE_MODE) {
-
// Unlikely to happen since switching to Edit mode saves content automatically.
// Checking anyway in case the save strategy changes in the future
if (!file.content) {
- unresolved++;
+ unresolved += 1;
continue;
}
}
@@ -366,15 +364,12 @@
};
this.state.conflictsData.files.forEach((file) => {
- let addFile;
-
- addFile = {
+ const addFile = {
old_path: file.old_path,
new_path: file.new_path
};
if (file.type === CONFLICT_TYPES.TEXT) {
-
// Submit only one data for type of editing
if (file.resolveMode === INTERACTIVE_RESOLVE_MODE) {
addFile.sections = file.resolutionData;
@@ -435,5 +430,4 @@
return this.state.conflictsData.files.some(f => f.type === CONFLICT_TYPES.TEXT);
}
};
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6
index 83520702f9b..a2d90f9ba47 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable new-cap, comma-dangle, no-new, semi */
+/* eslint-disable new-cap, comma-dangle, no-new */
/* global Vue */
/* global Flash */
@@ -29,10 +29,10 @@ $(() => {
'parallel-conflict-lines': gl.mergeConflicts.parallelConflictLines
},
computed: {
- conflictsCountText() { return mergeConflictsStore.getConflictsCountText() },
- readyToCommit() { return mergeConflictsStore.isReadyToCommit() },
- commitButtonText() { return mergeConflictsStore.getCommitButtonText() },
- showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent() }
+ conflictsCountText() { return mergeConflictsStore.getConflictsCountText(); },
+ readyToCommit() { return mergeConflictsStore.isReadyToCommit(); },
+ commitButtonText() { return mergeConflictsStore.getCommitButtonText(); },
+ showDiffViewTypeSwitcher() { return mergeConflictsStore.fileTextTypePresent(); }
},
created() {
mergeConflictsService
@@ -88,5 +88,5 @@ $(() => {
});
}
}
- })
+ });
});
diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6
index e89b35d5407..53e000d7e9e 100644
--- a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6
+++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-param-reassign, comma-dangle, padded-blocks */
+/* eslint-disable no-param-reassign, comma-dangle */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
@@ -10,5 +10,4 @@
}
}
};
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6
index a4aca85d460..0f475f62ee6 100644
--- a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6
+++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-param-reassign, quote-props, comma-dangle, padded-blocks */
+/* eslint-disable no-param-reassign, quote-props, comma-dangle */
((global) => {
global.mergeConflicts = global.mergeConflicts || {};
@@ -16,5 +16,4 @@
}
}
};
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index 244c2f6746c..09ee8dbe9d7 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, padded-blocks, max-len, prefer-arrow-callback */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
/* global MergeRequestTabs */
/*= require jquery.waitforimages */
@@ -6,7 +6,7 @@
/*= require merge_request_tabs */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.MergeRequest = (function() {
function MergeRequest(opts) {
@@ -130,7 +130,5 @@
};
return MergeRequest;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/merge_request_tabs.js.es6 b/app/assets/javascripts/merge_request_tabs.js.es6
index 860e7e066a0..4c8c28af755 100644
--- a/app/assets/javascripts/merge_request_tabs.js.es6
+++ b/app/assets/javascripts/merge_request_tabs.js.es6
@@ -237,13 +237,8 @@
}
this.diffsLoaded = true;
- const diffPage = new gl.Diff();
-
- const locationHash = gl.utils.getLocationHash();
- const anchoredDiff = locationHash && locationHash.split('_')[0];
- if (anchoredDiff) {
- diffPage.openAnchoredDiff(anchoredDiff, () => this.scrollToElement('#diffs'));
- }
+ new gl.Diff();
+ this.scrollToElement('#diffs');
},
});
}
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index 0305aeb07d9..7cc319e2f4e 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -1,11 +1,11 @@
-/* eslint-disable max-len, no-var, func-names, space-before-function-paren, vars-on-top, no-plusplus, comma-dangle, no-return-assign, consistent-return, no-param-reassign, one-var, one-var-declaration-per-line, quotes, prefer-template, no-else-return, prefer-arrow-callback, no-unused-vars, no-underscore-dangle, no-shadow, no-mixed-operators, template-curly-spacing, camelcase, default-case, wrap-iife, semi, padded-blocks */
+/* eslint-disable max-len, no-var, func-names, space-before-function-paren, vars-on-top, comma-dangle, no-return-assign, consistent-return, no-param-reassign, one-var, one-var-declaration-per-line, quotes, prefer-template, no-else-return, prefer-arrow-callback, no-unused-vars, no-underscore-dangle, no-shadow, no-mixed-operators, camelcase, default-case, wrap-iife */
/* global notify */
/* global notifyPermissions */
/* global merge_request_widget */
/* global Turbolinks */
((global) => {
- var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+ var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
const DEPLOYMENT_TEMPLATE = `<div class="mr-widget-heading" id="<%- id %>">
<div class="ci_widget ci-success">
@@ -90,7 +90,7 @@
const $ciSuccessIcon = $('.js-success-icon');
this.$ciSuccessIcon = $ciSuccessIcon.html();
$ciSuccessIcon.remove();
- }
+ };
MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) {
if (deleteSourceBranch == null) {
@@ -126,7 +126,9 @@
MergeRequestWidget.prototype.getMergeStatus = function() {
return $.get(this.opts.merge_check_url, function(data) {
- return $('.mr-state-widget').replaceWith(data);
+ var $html = $(data);
+ $('.mr-widget-body').replaceWith($html.find('.mr-widget-body'));
+ $('.mr-widget-footer').replaceWith($html.find('.mr-widget-footer'));
});
};
@@ -187,9 +189,9 @@
};
MergeRequestWidget.prototype.renderEnvironments = function(environments) {
- for (let i = 0; i < environments.length; i++) {
+ for (let i = 0; i < environments.length; i += 1) {
const environment = environments[i];
- if ($(`.mr-state-widget #${ environment.id }`).length) return;
+ if ($(`.mr-state-widget #${environment.id}`).length) return;
const $template = $(DEPLOYMENT_TEMPLATE);
if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove();
@@ -205,7 +207,7 @@
}
environment.ci_success_icon = this.$ciSuccessIcon;
const templateString = _.unescape($template[0].outerHTML);
- const template = _.template(templateString)(environment)
+ const template = _.template(templateString)(environment);
this.$widgetBody.before(template);
}
};
@@ -247,7 +249,5 @@
};
return MergeRequestWidget;
-
})();
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6
index 2b074994b4a..5969d2ba56b 100644
--- a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6
+++ b/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6
@@ -8,31 +8,42 @@
* temporarily.
* */
- if ($('.accept-mr-form').length) {
- $('.accept-mr-form').on('ajax:send', () => {
- $('.accept-mr-form :input').disable();
- });
+ $(document)
+ .off('ajax:send', '.accept-mr-form')
+ .on('ajax:send', '.accept-mr-form', () => {
+ $('.accept-mr-form :input').disable();
+ });
- $('.accept_merge_request').on('click', () => {
- $('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress');
- });
+ $(document)
+ .off('click', '.accept_merge_request')
+ .on('click', '.accept_merge_request', () => {
+ $('.js-merge-button').html('<i class="fa fa-spinner fa-spin"></i> Merge in progress');
+ });
- $('.merge_when_build_succeeds').on('click', () => {
- $('#merge_when_build_succeeds').val('1');
- });
+ $(document)
+ .off('click', '.merge_when_build_succeeds')
+ .on('click', '.merge_when_build_succeeds', () => {
+ $('#merge_when_build_succeeds').val('1');
+ });
- $('.js-merge-dropdown a').on('click', (e) => {
- e.preventDefault();
- $(this).closest('form').submit();
- });
- } else if ($('.rebase-in-progress').length) {
+ $(document)
+ .off('click', '.js-merge-dropdown a')
+ .on('click', '.js-merge-dropdown a', (e) => {
+ e.preventDefault();
+ $(e.target).closest('form').submit();
+ });
+ if ($('.rebase-in-progress').length) {
merge_request_widget.rebaseInProgress();
} else if ($('.rebase-mr-form').length) {
- $('.rebase-mr-form').on('ajax:send', () => {
+ $(document)
+ .off('ajax:send', '.rebase-mr-form')
+ .on('ajax:send', '.rebase-mr-form', () => {
$('.rebase-mr-form :input').disable();
});
- $('.js-rebase-button').on('click', () => {
+ $(document)
+ .off('click', '.js-rebase-button')
+ .on('click', '.js-rebase-button', () => {
$('.js-rebase-button').html("<i class='fa fa-spinner fa-spin'></i> Rebase in progress");
});
} else {
diff --git a/app/assets/javascripts/merged_buttons.js b/app/assets/javascripts/merged_buttons.js
index 9f8af46c715..527cdc9b698 100644
--- a/app/assets/javascripts/merged_buttons.js
+++ b/app/assets/javascripts/merged_buttons.js
@@ -1,7 +1,7 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, max-len */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.MergedButtons = (function() {
function MergedButtons() {
@@ -41,7 +41,5 @@
};
return MergedButtons;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js
index 42152362e60..7ce1259e015 100644
--- a/app/assets/javascripts/milestone.js
+++ b/app/assets/javascripts/milestone.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-use-before-define, camelcase, quotes, object-shorthand, no-shadow, no-unused-vars, comma-dangle, no-var, prefer-template, no-underscore-dangle, consistent-return, one-var, one-var-declaration-per-line, default-case, prefer-arrow-callback, max-len */
/* global Flash */
(function() {
@@ -193,7 +193,5 @@
};
return Milestone;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 28054b78249..7ab39ffbd05 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, comma-dangle, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */
/* global Vue */
/* global Issuable */
/* global ListMilestone */
@@ -181,7 +181,5 @@
}
return MilestoneSelect;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
index 90b3366f14b..80549532ea9 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
@@ -10,9 +10,9 @@
* The container should be the table element.
*
* The stage icon clicked needs to have the following HTML structure:
- * <div>
- * <button class="dropdown js-builds-dropdown-button"></button>
- * <div class="js-builds-dropdown-container"></div>
+ * <div class="dropdown">
+ * <button class="dropdown js-builds-dropdown-button" data-toggle="dropdown"></button>
+ * <div class="js-builds-dropdown-container dropdown-menu"></div>
* </div>
*/
(() => {
@@ -26,13 +26,11 @@
}
/**
- * Adds and removes the event listener.
+ * Adds the event listener when the dropdown is opened.
+ * All dropdown events are fired at the .dropdown-menu's parent element.
*/
bindEvents() {
- const dropdownButtonSelector = 'button.js-builds-dropdown-button';
-
- $(this.container).off('click', dropdownButtonSelector, this.getBuildsList)
- .on('click', dropdownButtonSelector, this.getBuildsList);
+ $(this.container).on('shown.bs.dropdown', this.getBuildsList);
}
/**
@@ -52,11 +50,14 @@
/**
* For the clicked stage, gets the list of builds.
*
- * @param {Object} e
+ * All dropdown events have a relatedTarget property,
+ * whose value is the toggling anchor element.
+ *
+ * @param {Object} e bootstrap dropdown event
* @return {Promise}
*/
getBuildsList(e) {
- const button = e.currentTarget;
+ const button = e.relatedTarget;
const endpoint = button.dataset.stageEndpoint;
return $.ajax({
diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js
index 6633f2c2709..514556ade0b 100644
--- a/app/assets/javascripts/namespace_select.js
+++ b/app/assets/javascripts/namespace_select.js
@@ -1,8 +1,8 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, padded-blocks, no-param-reassign, no-cond-assign, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, no-param-reassign, no-cond-assign, max-len */
/* global Api */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
window.NamespaceSelect = (function() {
function NamespaceSelect(opts) {
@@ -63,7 +63,6 @@
};
return NamespaceSelect;
-
})();
window.NamespaceSelects = (function() {
@@ -83,7 +82,5 @@
}
return NamespaceSelects;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index 20a68780cd5..a7ccd03b60c 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -1,8 +1,8 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, new-cap, no-plusplus, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, new-cap, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */
/* global Raphael */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.BranchGraph = (function() {
function BranchGraph(element1, options1) {
@@ -53,7 +53,7 @@
this.top = this.r.set();
this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320);
ref = this.commits;
- for (j = 0, len = ref.length; j < len; j++) {
+ for (j = 0, len = ref.length; j < len; j += 1) {
c = ref[j];
if (c.id in this.parents) {
c.isParent = true;
@@ -68,7 +68,7 @@
var c, j, len, p, ref, results;
ref = this.commits;
results = [];
- for (j = 0, len = ref.length; j < len; j++) {
+ for (j = 0, len = ref.length; j < len; j += 1) {
c = ref[j];
this.mtime = Math.max(this.mtime, c.time);
this.mspace = Math.max(this.mspace, c.space);
@@ -76,7 +76,7 @@
var l, len1, ref1, results1;
ref1 = c.parents;
results1 = [];
- for (l = 0, len1 = ref1.length; l < len1; l++) {
+ 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]));
@@ -96,7 +96,7 @@
// Skipping a few colors in the spectrum to get more contrast between colors
Raphael.getColor();
Raphael.getColor();
- results.push(k++);
+ results.push(k += 1);
}
return results;
};
@@ -113,7 +113,7 @@
fill: "#444"
});
ref = this.days;
- for (mm = j = 0, len = ref.length; j < len; mm = ++j) {
+ for (mm = j = 0, len = ref.length; j < len; mm = (j += 1)) {
day = ref[mm];
if (cuday !== day[0] || cumonth !== day[1]) {
// Dates
@@ -286,7 +286,7 @@
r = this.r;
ref = commit.parents;
results = [];
- for (i = j = 0, len = ref.length; j < len; i = ++j) {
+ for (i = j = 0, len = ref.length; j < len; i = (j += 1)) {
parent = ref[i];
parentCommit = this.preparedCommits[parent[0]];
parentY = this.offsetY + this.unitTime * parentCommit.time;
@@ -346,7 +346,6 @@
};
return BranchGraph;
-
})();
Raphael.prototype.commitTooltip = function(x, y, commit) {
@@ -399,7 +398,7 @@
words = content.split(" ");
x = 0;
s = [];
- for (j = 0, len = words.length; j < len; j++) {
+ for (j = 0, len = words.length; j < len; j += 1) {
word = words[j];
if (x + (word.length * letterWidth) > width) {
s.push("\n");
@@ -422,5 +421,4 @@
y: h
});
};
-
}).call(this);
diff --git a/app/assets/javascripts/network/network.js b/app/assets/javascripts/network/network.js
index 2367d2497b2..37bf6436fd1 100644
--- a/app/assets/javascripts/network/network.js
+++ b/app/assets/javascripts/network/network.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */
/* global BranchGraph */
(function() {
@@ -16,7 +16,5 @@
}
return Network;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js
index 17833d3419a..2e6eb83cec7 100644
--- a/app/assets/javascripts/network/network_bundle.js
+++ b/app/assets/javascripts/network/network_bundle.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, camelcase, comma-dangle, consistent-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, camelcase, comma-dangle, consistent-return, max-len */
/* global Network */
/* global ShortcutsNetwork */
@@ -23,5 +23,4 @@
});
return new ShortcutsNetwork(network_graph.branch_graph);
});
-
}).call(this);
diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js
index 29a323dd4c6..7f763c13b50 100644
--- a/app/assets/javascripts/new_branch_form.js
+++ b/app/assets/javascripts/new_branch_form.js
@@ -1,7 +1,7 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, one-var, space-before-blocks, prefer-rest-params, max-len, vars-on-top, no-plusplus, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, one-var, prefer-rest-params, max-len, vars-on-top, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
- indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
this.NewBranchForm = (function() {
function NewBranchForm(form, availableRefs) {
@@ -99,7 +99,5 @@
};
return NewBranchForm;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js
index 8fb8f3e4a5f..41eea78a3e6 100644
--- a/app/assets/javascripts/new_commit_form.js
+++ b/app/assets/javascripts/new_commit_form.js
@@ -1,6 +1,6 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-return-assign, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-return-assign, max-len */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.NewCommitForm = (function() {
function NewCommitForm(form) {
@@ -29,7 +29,5 @@
};
return NewCommitForm;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index a8b9a352870..9db830a7ada 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1,8 +1,9 @@
-/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks */
+/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */
/* global Flash */
/* global GLForm */
/* global Autosave */
/* global ResolveService */
+/* global mrRefreshWidgetUrl */
/*= require autosave */
/*= require autosize */
@@ -13,7 +14,7 @@
/*= require task_list */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.Notes = (function() {
const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
@@ -52,6 +53,12 @@
this.setupMainTargetNoteForm();
this.initTaskList();
this.collapseLongCommitList();
+
+ // We are in the Merge Requests page so we need another edit form for Changes tab
+ if (gl.utils.getPagePath(1) === 'merge_requests') {
+ $('.note-edit-form').clone()
+ .addClass('mr-note-edit-form').insertAfter('.note-edit-form');
+ }
}
Notes.prototype.addBinding = function() {
@@ -63,7 +70,7 @@
// change note in UI after update
$(document).on("ajax:success", "form.edit-note", this.updateNote);
// Edit note link
- $(document).on("click", ".js-note-edit", this.showEditForm);
+ $(document).on("click", ".js-note-edit", this.showEditForm.bind(this));
$(document).on("click", ".note-edit-cancel", this.cancelEdit);
// Reopen and close actions for Issue/MR combined with note form submit
$(document).on("click", ".js-comment-button", this.updateCloseButton);
@@ -213,7 +220,6 @@
})(this));
};
-
/*
Increase @pollingInterval up to 120 seconds on every function call,
if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
@@ -237,6 +243,15 @@
return this.initRefresh();
};
+ Notes.prototype.handleCreateChanges = function(note) {
+ if (typeof note === 'undefined') {
+ return;
+ }
+
+ if (note.commands_changes && note.commands_changes.indexOf('merge') !== -1) {
+ $.get(mrRefreshWidgetUrl);
+ }
+ };
/*
Render note in main comments area.
@@ -277,7 +292,6 @@
}
};
-
/*
Check if note does not exists on page
*/
@@ -290,7 +304,6 @@
return this.view === 'parallel';
};
-
/*
Render note in discussion area.
@@ -341,7 +354,6 @@
return this.updateNotesCount(1);
};
-
/*
Called in response the main target form has been successfully submitted.
@@ -373,7 +385,6 @@
return form.find(".js-note-text").trigger("input");
};
-
/*
Shows the main form and does some setup on it.
@@ -398,7 +409,6 @@
return this.parentTimeline = form.parents('.timeline');
};
-
/*
General note form setup.
@@ -415,7 +425,6 @@
return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]);
};
-
/*
Called in response to the new note form being submitted
@@ -423,6 +432,7 @@
*/
Notes.prototype.addNote = function(xhr, note, status) {
+ this.handleCreateChanges(note);
return this.renderNote(note);
};
@@ -430,7 +440,6 @@
return new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', this.parentTimeline);
};
-
/*
Called in response to the new note form being submitted
@@ -455,7 +464,6 @@
this.removeDiscussionNoteForm($form);
};
-
/*
Called in response to the edit note form being submitted
@@ -466,6 +474,7 @@
var $html, $note_li;
// Convert returned HTML to a jQuery object so we can modify it further
$html = $(note.html);
+ this.revertNoteEditForm();
gl.utils.localTimeAgo($('.js-timeago', $html));
$html.renderGFM();
$html.find('.js-task-list-container').taskList('enable');
@@ -479,51 +488,56 @@
}
};
+ Notes.prototype.checkContentToAllowEditing = function($el) {
+ var initialContent = $el.find('.original-note-content').text().trim();
+ var currentContent = $el.find('.note-textarea').val();
+ var isAllowed = true;
+
+ if (currentContent === initialContent) {
+ this.removeNoteEditForm($el);
+ }
+ else {
+ var $buttons = $el.find('.note-form-actions');
+ var isWidgetVisible = gl.utils.isInViewport($el.get(0));
+
+ if (!isWidgetVisible) {
+ gl.utils.scrollToElement($el);
+ }
+
+ $el.find('.js-edit-warning').show();
+ isAllowed = false;
+ }
+
+ return isAllowed;
+ };
/*
Called in response to clicking the edit note link
Replaces the note text with the note edit form
Adds a data attribute to the form with the original content of the note for cancellations
- */
-
+ */
Notes.prototype.showEditForm = function(e, scrollTo, myLastNote) {
- var $noteText, done, form, note;
e.preventDefault();
- note = $(this).closest(".note");
- note.addClass("is-editting");
- form = note.find(".note-edit-form");
- form.addClass('current-note-edit-form');
- // Show the attachment delete link
- note.find(".js-note-attachment-delete").show();
- done = function($noteText) {
- var noteTextVal;
- // Neat little trick to put the cursor at the end
- noteTextVal = $noteText.val();
- // Store the original note text in a data attribute to retrieve if a user cancels edit.
- form.find('form.edit-note').data('original-note', noteTextVal);
- return $noteText.val('').val(noteTextVal);
- };
- new GLForm(form);
- if ((scrollTo != null) && (myLastNote != null)) {
- // scroll to the bottom
- // so the open of the last element doesn't make a jump
- $('html, body').scrollTop($(document).height());
- return $('html, body').animate({
- scrollTop: myLastNote.offset().top - 150
- }, 500, function() {
- var $noteText;
- $noteText = form.find(".js-note-text");
- $noteText.focus();
- return done($noteText);
- });
- } else {
- $noteText = form.find('.js-note-text');
- $noteText.focus();
- return done($noteText);
+
+ var $target = $(e.target);
+ var $editForm = $(this.getEditFormSelector($target));
+ var $note = $target.closest('.note');
+ var $currentlyEditing = $('.note.is-editting:visible');
+
+ if ($currentlyEditing.length) {
+ var isEditAllowed = this.checkContentToAllowEditing($currentlyEditing);
+
+ if (!isEditAllowed) {
+ return;
+ }
}
- };
+ $note.find('.js-note-attachment-delete').show();
+ $editForm.addClass('current-note-edit-form');
+ $note.addClass('is-editting');
+ this.putEditFormInPlace($target);
+ };
/*
Called in response to clicking the edit note link
@@ -532,22 +546,43 @@
*/
Notes.prototype.cancelEdit = function(e) {
- var note;
e.preventDefault();
- note = $(e.target).closest('.note');
+ var $target = $(e.target);
+ var note = $target.closest('.note');
+ note.find('.js-edit-warning').hide();
+ this.revertNoteEditForm($target);
return this.removeNoteEditForm(note);
};
+ Notes.prototype.revertNoteEditForm = function($target) {
+ $target = $target || $('.note.is-editting:visible');
+ var selector = this.getEditFormSelector($target);
+ var $editForm = $(selector);
+
+ $editForm.insertBefore('.notes-form');
+ $editForm.find('.js-comment-button').enable();
+ $editForm.find('.js-edit-warning').hide();
+ };
+
+ Notes.prototype.getEditFormSelector = function($el) {
+ var selector = '.note-edit-form:not(.mr-note-edit-form)';
+
+ if ($el.parents('#diffs').length) {
+ selector = '.note-edit-form.mr-note-edit-form';
+ }
+
+ return selector;
+ };
+
Notes.prototype.removeNoteEditForm = function(note) {
- var form;
- form = note.find(".current-note-edit-form");
- note.removeClass("is-editting");
- form.removeClass("current-note-edit-form");
+ var form = note.find('.current-note-edit-form');
+ note.removeClass('is-editting');
+ form.removeClass('current-note-edit-form');
+ form.find('.js-edit-warning').hide();
// Replace markdown textarea text with original note text.
- return form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'));
+ return form.find('.js-note-text').val(form.find('form.edit-note').data('original-note'));
};
-
/*
Called in response to deleting a note of any kind.
@@ -587,7 +622,6 @@
return this.updateNotesCount(-1);
};
-
/*
Called in response to clicking the delete attachment link
@@ -604,7 +638,6 @@
return note.find(".current-note-edit-form").remove();
};
-
/*
Called when clicking on the "reply" button for a diff line.
@@ -624,7 +657,6 @@
return this.setupDiscussionNoteForm(replyLink, form);
};
-
/*
Shows the diff or discussion form and does some setup on it.
@@ -666,7 +698,6 @@
.addClass("discussion-form js-discussion-note-form");
};
-
/*
Called when clicking on the "add a comment" button on the side of a diff line.
@@ -723,7 +754,6 @@
}
};
-
/*
Called in response to "cancel" on a diff note form.
@@ -757,7 +787,6 @@
return this.removeDiscussionNoteForm(form);
};
-
/*
Called after an attachment file has been selected.
@@ -772,7 +801,6 @@
return form.find(".js-attachment-filename").text(filename);
};
-
/*
Called when the tab visibility changes
*/
@@ -837,19 +865,50 @@
Notes.prototype.initTaskList = function() {
this.enableTaskList();
- return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList);
+ return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList.bind(this));
};
Notes.prototype.enableTaskList = function() {
return $('.note .js-task-list-container').taskList('enable');
};
- Notes.prototype.updateTaskList = function() {
- return $('form', this).submit();
+ Notes.prototype.putEditFormInPlace = function($el) {
+ var $editForm = $(this.getEditFormSelector($el));
+ var $note = $el.closest('.note');
+
+ $editForm.insertAfter($note.find('.note-text'));
+
+ var $originalContentEl = $note.find('.original-note-content');
+ var originalContent = $originalContentEl.text().trim();
+ var postUrl = $originalContentEl.data('post-url');
+ var targetId = $originalContentEl.data('target-id');
+ var targetType = $originalContentEl.data('target-type');
+
+ new GLForm($editForm.find('form'));
+
+ $editForm.find('form')
+ .attr('action', postUrl)
+ .attr('data-remote', 'true');
+ $editForm.find('.js-form-target-id').val(targetId);
+ $editForm.find('.js-form-target-type').val(targetType);
+ $editForm.find('.js-note-text').focus().val(originalContent);
+ $editForm.find('.js-md-write-button').trigger('click');
+ $editForm.find('.referenced-users').hide();
+ };
+
+ Notes.prototype.updateTaskList = function(e) {
+ var $target = $(e.target);
+ var $list = $target.closest('.js-task-list-container');
+ var $editForm = $(this.getEditFormSelector($target));
+ var $note = $list.closest('.note');
+
+ this.putEditFormInPlace($list);
+ $editForm.find('#note_note').val($note.find('.original-task-list').val());
+ $('form', $list).submit();
};
Notes.prototype.updateNotesCount = function(updateCount) {
- return this.notesCountBadge.text(parseInt(this.notesCountBadge.text()) + updateCount);
+ return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
};
Notes.prototype.resolveDiscussion = function() {
@@ -894,7 +953,5 @@
};
return Notes;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js
index 5d0d594073d..926dc35fee8 100644
--- a/app/assets/javascripts/notifications_dropdown.js
+++ b/app/assets/javascripts/notifications_dropdown.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, prefer-arrow-callback, no-else-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, prefer-arrow-callback, no-else-return, max-len */
/* global Flash */
(function() {
@@ -27,7 +27,5 @@
}
return NotificationsDropdown;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js
index 2034f9a748a..c3d7cc0adfb 100644
--- a/app/assets/javascripts/notifications_form.js
+++ b/app/assets/javascripts/notifications_form.js
@@ -1,6 +1,6 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, newline-per-chained-call, comma-dangle, consistent-return, prefer-arrow-callback, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, newline-per-chained-call, comma-dangle, consistent-return, prefer-arrow-callback, max-len */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.NotificationsForm = (function() {
function NotificationsForm() {
@@ -53,7 +53,5 @@
};
return NotificationsForm;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6
index 0b09ad113a3..43263368494 100644
--- a/app/assets/javascripts/pipelines.js.es6
+++ b/app/assets/javascripts/pipelines.js.es6
@@ -1,12 +1,10 @@
-/* eslint-disable no-new, guard-for-in, no-restricted-syntax, no-continue, padded-blocks, no-param-reassign, max-len */
+/* eslint-disable no-new, guard-for-in, no-restricted-syntax, no-continue, no-param-reassign, max-len */
//= require lib/utils/bootstrap_linked_tabs
((global) => {
-
class Pipelines {
constructor(options = {}) {
-
if (options.initTabs && options.tabsOptions) {
new global.LinkedTabs(options.tabsOptions);
}
@@ -37,5 +35,4 @@
}
global.Pipelines = Pipelines;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js
index 89f7e976934..07eea98e737 100644
--- a/app/assets/javascripts/preview_markdown.js
+++ b/app/assets/javascripts/preview_markdown.js
@@ -7,6 +7,7 @@
//
(function () {
var lastTextareaPreviewed;
+ var lastTextareaHeight = null;
var markdownPreview;
var previewButtonSelector;
var writeButtonSelector;
@@ -104,10 +105,14 @@
if (!$form) {
return;
}
+
lastTextareaPreviewed = $form.find('textarea.markdown-area');
+ lastTextareaHeight = lastTextareaPreviewed.height();
+
// toggle tabs
$form.find(writeButtonSelector).parent().removeClass('active');
$form.find(previewButtonSelector).parent().addClass('active');
+
// toggle content
$form.find('.md-write-holder').hide();
$form.find('.md-preview-holder').show();
@@ -119,9 +124,15 @@
return;
}
lastTextareaPreviewed = null;
+
+ if (lastTextareaHeight) {
+ $form.find('textarea.markdown-area').height(lastTextareaHeight);
+ }
+
// toggle tabs
$form.find(writeButtonSelector).parent().addClass('active');
$form.find(previewButtonSelector).parent().removeClass('active');
+
// toggle content
$form.find('.md-write-holder').show();
$form.find('textarea.markdown-area').focus();
diff --git a/app/assets/javascripts/profile/gl_crop.js.es6 b/app/assets/javascripts/profile/gl_crop.js.es6
index b4b6da41f63..42e9847af91 100644
--- a/app/assets/javascripts/profile/gl_crop.js.es6
+++ b/app/assets/javascripts/profile/gl_crop.js.es6
@@ -1,14 +1,12 @@
-/* eslint-disable no-useless-escape, max-len, padded-blocks, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, no-plusplus, new-parens, semi */
+/* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */
((global) => {
-
// Matches everything but the file name
const FILENAMEREGEX = /^.*[\\\/]/;
class GitLabCrop {
constructor(input, { filename, previewImage, modalCrop, pickImageEl, uploadImageBtn, modalCropImg,
exportWidth = 200, exportHeight = 200, cropBoxWidth = 200, cropBoxHeight = 200 } = {}) {
-
this.onUploadImageBtnClick = this.onUploadImageBtnClick.bind(this);
this.onModalHide = this.onModalHide.bind(this);
this.onModalShow = this.onModalShow.bind(this);
@@ -136,7 +134,7 @@
var array, binary, i, k, len, v;
binary = atob(dataURL.split(',')[1]);
array = [];
- for (k = i = 0, len = binary.length; i < len; k = ++i) {
+ for (k = i = 0, len = binary.length; i < len; k = (i += 1)) {
v = binary[k];
array.push(binary.charCodeAt(k));
}
@@ -169,6 +167,5 @@
return this.each(function() {
return $(this).data('glcrop', new GitLabCrop(this, opts));
});
- }
-
+ };
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6
index aef2e3a3fa8..5aec9c813fe 100644
--- a/app/assets/javascripts/profile/profile.js.es6
+++ b/app/assets/javascripts/profile/profile.js.es6
@@ -1,8 +1,7 @@
-/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len, padded-blocks */
+/* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */
/* global Flash */
((global) => {
-
class Profile {
constructor({ form } = {}) {
this.onSubmitForm = this.onSubmitForm.bind(this);
@@ -37,6 +36,7 @@
}
onSubmitForm(e) {
+ e.preventDefault();
return this.saveForm();
}
@@ -95,5 +95,4 @@
return new Profile();
}
});
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index fcf3a4af956..7cf630a1d76 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-else-return, newline-per-chained-call, no-shadow, semi, vars-on-top, indent, prefer-template, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
/* global Cookies */
/* global Turbolinks */
/* global ProjectSelect */
@@ -94,11 +94,11 @@
return $el.text().trim();
},
clicked: function(selected, $el, e) {
- e.preventDefault()
+ e.preventDefault();
if ($('input[name="ref"]').length) {
- var $form = $dropdown.closest('form'),
- action = $form.attr('action'),
- divider = action.indexOf('?') < 0 ? '?' : '&';
+ var $form = $dropdown.closest('form');
+ var action = $form.attr('action');
+ var divider = action.indexOf('?') < 0 ? '?' : '&';
Turbolinks.visit(action + '' + divider + '' + $form.serialize());
}
}
@@ -107,7 +107,5 @@
};
return Project;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/project_avatar.js
index 84f28ede4bf..a6d3ba9eb86 100644
--- a/app/assets/javascripts/project_avatar.js
+++ b/app/assets/javascripts/project_avatar.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-useless-escape, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */
(function() {
this.ProjectAvatar = (function() {
function ProjectAvatar() {
@@ -16,7 +16,5 @@
}
return ProjectAvatar;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 1bd232314d0..04fe84683f3 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -1,8 +1,8 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, no-plusplus, prefer-template, no-unused-vars, no-return-assign, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */
/* global fuzzaldrinPlus */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.ProjectFindFile = (function() {
var highlighter;
@@ -71,7 +71,7 @@
var blobItemUrl, filePath, html, i, j, len, matches, results;
this.element.find(".tree-table > tbody").empty();
results = [];
- for (i = j = 0, len = filePaths.length; j < len; i = ++j) {
+ for (i = j = 0, len = filePaths.length; j < len; i = (j += 1)) {
filePath = filePaths[i];
if (i === 20) {
break;
@@ -92,7 +92,7 @@
lastIndex = 0;
highlightText = "";
matchedChars = [];
- for (j = 0, len = matches.length; j < len; j++) {
+ for (j = 0, len = matches.length; j < len; j += 1) {
matchIndex = matches[j];
unmatched = text.substring(lastIndex, matchIndex);
if (unmatched) {
@@ -167,7 +167,5 @@
};
return ProjectFindFile;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js
index 4aedc9a2330..208f25a0e33 100644
--- a/app/assets/javascripts/project_fork.js
+++ b/app/assets/javascripts/project_fork.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */
(function() {
this.ProjectFork = (function() {
function ProjectFork() {
@@ -9,7 +9,5 @@
}
return ProjectFork;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js
index 02dafcfb865..6614d8952cd 100644
--- a/app/assets/javascripts/project_import.js
+++ b/app/assets/javascripts/project_import.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */
/* global Turbolinks */
(function() {
@@ -10,7 +10,5 @@
}
return ProjectImport;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/project_label_subscription.js.es6 b/app/assets/javascripts/project_label_subscription.js.es6
index b8d6a198996..8365f7118d5 100644
--- a/app/assets/javascripts/project_label_subscription.js.es6
+++ b/app/assets/javascripts/project_label_subscription.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable wrap-iife, func-names, space-before-function-paren, object-shorthand, comma-dangle, one-var, one-var-declaration-per-line, no-restricted-syntax, prefer-const, max-len, no-param-reassign, padded-blocks */
+/* eslint-disable wrap-iife, func-names, space-before-function-paren, object-shorthand, comma-dangle, one-var, one-var-declaration-per-line, no-restricted-syntax, max-len, no-param-reassign */
(function(global) {
class ProjectLabelSubscription {
@@ -38,8 +38,8 @@
this.$buttons.attr('data-status', newStatus);
this.$buttons.find('> span').text(newAction);
- for (let button of this.$buttons) {
- let $button = $(button);
+ for (const button of this.$buttons) {
+ const $button = $(button);
if ($button.attr('data-original-title')) {
$button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle');
@@ -50,5 +50,4 @@
}
global.ProjectLabelSubscription = ProjectLabelSubscription;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js
index 7fc611d0dad..3aa6f6771ce 100644
--- a/app/assets/javascripts/project_new.js
+++ b/app/assets/javascripts/project_new.js
@@ -1,6 +1,7 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-unused-vars, one-var, indent, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, radix, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, one-var, no-underscore-dangle, prefer-template, no-else-return, prefer-arrow-callback, max-len */
+
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.ProjectNew = (function() {
function ProjectNew() {
@@ -14,18 +15,29 @@
return $('.save-project-loader').show();
};
})(this));
+
+ this.initVisibilitySelect();
+
this.toggleSettings();
this.toggleSettingsOnclick();
this.toggleRepoVisibility();
}
+ ProjectNew.prototype.initVisibilitySelect = function() {
+ const visibilityContainer = document.querySelector('.js-visibility-select');
+ if (!visibilityContainer) return;
+ const visibilitySelect = new gl.VisibilitySelect(visibilityContainer);
+ visibilitySelect.init();
+ };
+
ProjectNew.prototype.toggleSettings = function() {
var self = this;
this.$selects.each(function () {
- var $select = $(this),
- className = $select.data('field').replace(/_/g, '-')
- .replace('access-level', 'feature');
+ var $select = $(this);
+ var className = $select.data('field')
+ .replace(/_/g, '-')
+ .replace('access-level', 'feature');
self._showOrHide($select, '.' + className);
});
};
@@ -45,9 +57,9 @@
};
ProjectNew.prototype.toggleRepoVisibility = function () {
- var $repoAccessLevel = $('.js-repo-access-level select'),
- containerRegistry = document.querySelectorAll('.js-container-registry')[0],
- containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
+ var $repoAccessLevel = $('.js-repo-access-level select');
+ var containerRegistry = document.querySelectorAll('.js-container-registry')[0];
+ var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
.nextAll()
@@ -55,11 +67,11 @@
$repoAccessLevel.off('change')
.on('change', function () {
- var selectedVal = parseInt($repoAccessLevel.val());
+ var selectedVal = parseInt($repoAccessLevel.val(), 10);
this.$repoSelects.each(function () {
- var $this = $(this),
- repoSelectVal = parseInt($this.val());
+ var $this = $(this);
+ var repoSelectVal = parseInt($this.val(), 10);
$this.find('option').show();
@@ -88,7 +100,5 @@
};
return ProjectNew;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js
index 38bc2e1c3a0..7b5e9953598 100644
--- a/app/assets/javascripts/project_select.js
+++ b/app/assets/javascripts/project_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */
/* global Api */
(function() {
@@ -100,7 +100,5 @@
}
return ProjectSelect;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js
index eaf4c03d573..aad130cf267 100644
--- a/app/assets/javascripts/project_show.js
+++ b/app/assets/javascripts/project_show.js
@@ -1,12 +1,11 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife */
+
(function() {
this.ProjectShow = (function() {
function ProjectShow() {}
return ProjectShow;
-
})();
-
}).call(this);
// I kept class for future
diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js
index 4548dc68fe1..69a11dfaf39 100644
--- a/app/assets/javascripts/projects_list.js
+++ b/app/assets/javascripts/projects_list.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, object-shorthand, quotes, no-var, one-var, one-var-declaration-per-line, prefer-arrow-callback, consistent-return, no-unused-vars, camelcase, prefer-template, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, object-shorthand, quotes, no-var, one-var, one-var-declaration-per-line, prefer-arrow-callback, consistent-return, no-unused-vars, camelcase, prefer-template, comma-dangle, max-len */
(function() {
window.ProjectsList = {
@@ -47,5 +47,4 @@
});
}
};
-
}).call(this);
diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6
index 4aef1c84b56..e7fff57ff45 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6
@@ -1,7 +1,7 @@
-/* eslint-disable arrow-parens, no-param-reassign, no-irregular-whitespace, object-shorthand, no-else-return, comma-dangle, semi, padded-blocks, max-len */
+/* eslint-disable arrow-parens, no-param-reassign, object-shorthand, no-else-return, comma-dangle, max-len */
(global => {
- global.gl = global.gl || {};
+ global.gl = global.gl || {};
gl.ProtectedBranchAccessDropdown = class {
constructor(options) {
@@ -25,6 +25,5 @@
}
});
}
- }
-
+ };
})(window);
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_create.js.es6
index f26fba979a4..57ea2f52814 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js.es6
@@ -1,8 +1,8 @@
-/* eslint-disable no-new, arrow-parens, no-param-reassign, no-irregular-whitespace, comma-dangle, padded-blocks, semi, max-len */
+/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */
/* global ProtectedBranchDropdown */
(global => {
- global.gl = global.gl || {};
+ global.gl = global.gl || {};
gl.ProtectedBranchCreate = class {
constructor() {
@@ -44,7 +44,6 @@
// This will run after clicked callback
onSelect() {
-
// Enable submit button
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]');
@@ -52,6 +51,5 @@
this.$form.find('input[type="submit"]').attr('disabled', !($branchInput.val() && $allowedToMergeInput.length && $allowedToPushInput.length));
}
- }
-
+ };
})(window);
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
index 4ff2fa5a80f..149e511451e 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
@@ -1,8 +1,8 @@
-/* eslint-disable no-new, arrow-parens, no-param-reassign, no-irregular-whitespace, padded-blocks, comma-dangle, no-trailing-spaces, semi, max-len */
+/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, max-len */
/* global Flash */
(global => {
- global.gl = global.gl || {};
+ global.gl = global.gl || {};
gl.ProtectedBranchEdit = class {
constructor(options) {
@@ -14,7 +14,6 @@
}
buildDropdowns() {
-
// Allowed to merge dropdown
new gl.ProtectedBranchAccessDropdown({
$dropdown: this.$allowedToMergeDropdown,
@@ -63,6 +62,5 @@
}
});
}
- }
-
+ };
})(window);
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6
index b6972ef2e16..336fa6c57a7 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6
@@ -1,10 +1,10 @@
-/* eslint-disable arrow-parens, no-param-reassign, no-irregular-whitespace, no-new, comma-dangle, semi, padded-blocks, max-len */
+/* eslint-disable arrow-parens, no-param-reassign, no-new, comma-dangle */
(global => {
- global.gl = global.gl || {};
+ global.gl = global.gl || {};
gl.ProtectedBranchEditList = class {
- constructor() {
+ constructor() {
this.$wrap = $('.protected-branches-list');
// Build edit forms
@@ -14,6 +14,5 @@
});
});
}
- }
-
+ };
})(window);
diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js
index bbb2f186655..0caf8ba4344 100644
--- a/app/assets/javascripts/render_gfm.js
+++ b/app/assets/javascripts/render_gfm.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, max-len */
// Render Gitlab flavoured Markdown
//
// Delegates to syntax highlight and render math
@@ -12,5 +12,4 @@
$(document).on('ready page:load', function() {
return $('body').renderGFM();
});
-
}).call(this);
diff --git a/app/assets/javascripts/render_math.js b/app/assets/javascripts/render_math.js
index 209e7a8661d..6cef449babf 100644
--- a/app/assets/javascripts/render_math.js
+++ b/app/assets/javascripts/render_math.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, padded-blocks, max-len, no-console */
+/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-undef, no-else-return, prefer-arrow-callback, max-len, no-console */
// Renders math using KaTeX in any element with the
// `js-render-math` class
//
@@ -51,5 +51,4 @@
});
}
};
-
}).call(this);
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index b1e844b7302..76a0f993ea0 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -1,8 +1,8 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-unused-vars, semi, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, object-shorthand, comma-dangle, no-else-return, no-param-reassign, max-len */
/* global Cookies */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.Sidebar = (function() {
function Sidebar(currentUser) {
@@ -18,7 +18,7 @@
$('.dropdown').off('loading.gl.dropdown');
$('.dropdown').off('loaded.gl.dropdown');
$(document).off('click', '.js-sidebar-toggle');
- }
+ };
Sidebar.prototype.addEventListeners = function() {
this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked);
@@ -200,7 +200,5 @@
};
return Sidebar;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js
index 4b6ebadeac7..489e567259c 100644
--- a/app/assets/javascripts/search.js
+++ b/app/assets/javascripts/search.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */
/* global Api */
(function() {
@@ -12,6 +12,9 @@
selectable: true,
filterable: true,
fieldName: 'group_id',
+ search: {
+ fields: ['name']
+ },
data: function(term, callback) {
return Api.groups(term, {}, function(data) {
data.unshift({
@@ -40,6 +43,9 @@
selectable: true,
filterable: true,
fieldName: 'project_id',
+ search: {
+ fields: ['name']
+ },
data: function(term, callback) {
return Api.projects(term, 'id', function(data) {
data.unshift({
@@ -90,7 +96,5 @@
};
return Search;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6
index 437f5dbbf7d..6250e75d407 100644
--- a/app/assets/javascripts/search_autocomplete.js.es6
+++ b/app/assets/javascripts/search_autocomplete.js.es6
@@ -1,7 +1,6 @@
-/* eslint-disable comma-dangle, no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, no-plusplus, prefer-template, quotes, class-methods-use-this, no-unused-expressions, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, padded-blocks, no-extra-semi, indent, max-len */
+/* eslint-disable comma-dangle, no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-unused-expressions, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */
((global) => {
-
const KEYCODE = {
ESCAPE: 27,
BACKSPACE: 8,
@@ -70,12 +69,17 @@
search: {
fields: ['text']
},
+ id: this.getSearchText,
data: this.getData.bind(this),
selectable: true,
clicked: this.onClick.bind(this)
});
}
+ getSearchText(selectedObject, el) {
+ return selectedObject.id ? selectedObject.text : '';
+ }
+
getData(term, callback) {
var _this, contents, jqXHR;
_this = this;
@@ -105,7 +109,7 @@
data = [];
// List results
firstCategory = true;
- for (i = 0, len = response.length; i < len; i++) {
+ for (i = 0, len = response.length; i < len; i += 1) {
suggestion = response[i];
// Add group header before list each group
if (lastCategory !== suggestion.category) {
@@ -142,8 +146,9 @@
}
getCategoryContents() {
- var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils;
+ var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName, utils;
userId = gon.current_user_id;
+ userName = gon.current_username;
utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
if (utils.isInGroupsPage() && groupOptions) {
options = groupOptions[utils.getGroupSlug()];
@@ -158,10 +163,10 @@
header: "" + name
}, {
text: 'Issues assigned to me',
- url: issuesPath + "/?assignee_id=" + userId
+ url: issuesPath + "/?assignee_username=" + userName
}, {
text: "Issues I've created",
- url: issuesPath + "/?author_id=" + userId
+ url: issuesPath + "/?author_username=" + userName
}, 'separator', {
text: 'Merge requests assigned to me',
url: mrPath + "/?assignee_id=" + userId
@@ -215,7 +220,7 @@
this.dropdown.addClass('open').trigger('shown.bs.dropdown');
return this.searchInput.removeClass('disabled');
}
- };
+ }
// Saves last length of the entered text
onSearchInputKeyDown() {
@@ -279,12 +284,12 @@
return this.searchInput.val();
}
- onClearInputClick(e) {
+ onClearInputClick(e) {
e.preventDefault();
return this.searchInput.val('').focus();
}
- onSearchInputBlur(e) {
+ onSearchInputBlur(e) {
this.isFocused = false;
this.wrap.removeClass('search-active');
// If input is blank then restore state
@@ -304,12 +309,12 @@
hasLocationBadge() {
return this.wrap.is('.has-location-badge');
- };
+ }
restoreOriginalState() {
var i, input, inputs, len;
inputs = Object.keys(this.originalState);
- for (i = 0, len = inputs.length; i < len; i++) {
+ for (i = 0, len = inputs.length; i < len; i += 1) {
input = inputs[i];
this.getElement("#" + input).val(this.originalState[input]);
}
@@ -330,7 +335,7 @@
var i, input, inputs, len, results;
inputs = Object.keys(this.originalState);
results = [];
- for (i = 0, len = inputs.length; i < len; i++) {
+ for (i = 0, len = inputs.length; i < len; i += 1) {
input = inputs[i];
// _location isnt a input
if (input === '_location') {
@@ -360,11 +365,11 @@
var html;
html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>";
return this.dropdownContent.html(html);
- };
+ }
onClick(item, $el, e) {
if (location.pathname.indexOf(item.url) !== -1) {
- e.preventDefault();
+ if (!e.metaKey) e.preventDefault();
if (!this.badgePresent) {
if (item.category === 'Projects') {
this.projectInputEl.val(item.id);
@@ -383,8 +388,7 @@
this.disableAutocomplete();
return this.searchInput.val('').focus();
}
- };
-
+ }
}
global.SearchAutocomplete = SearchAutocomplete;
@@ -425,5 +429,4 @@
};
}
});
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js
index 5ea00f408f4..c56ee429b8e 100644
--- a/app/assets/javascripts/shortcuts.js
+++ b/app/assets/javascripts/shortcuts.js
@@ -1,10 +1,10 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-plusplus, no-else-return, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */
/* global Turbolinks */
/* global findFileURL */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.Shortcuts = (function() {
function Shortcuts(skipResetBindings) {
@@ -51,7 +51,7 @@
var i, l, len, results;
if (location && location.length > 0) {
results = [];
- for (i = 0, len = location.length; i < len; i++) {
+ for (i = 0, len = location.length; i < len; i += 1) {
l = location[i];
results.push($(l).show());
}
@@ -78,7 +78,6 @@
};
return Shortcuts;
-
})();
$(document).on('click.more_help', '.js-more-help-button', function(e) {
@@ -99,5 +98,4 @@
}
};
})();
-
}).call(this);
diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/shortcuts_blob.js
index c26903038b4..d50ddd98de1 100644
--- a/app/assets/javascripts/shortcuts_blob.js
+++ b/app/assets/javascripts/shortcuts_blob.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, consistent-return, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, consistent-return */
/* global Shortcuts */
/* global Mousetrap */
@@ -25,7 +25,5 @@
};
return ShortcutsBlob;
-
})(Shortcuts);
-
}).call(this);
diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js
index 4549742bbcb..603fefbf15a 100644
--- a/app/assets/javascripts/shortcuts_dashboard_navigation.js
+++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */
/* global Shortcuts */
@@ -36,7 +36,5 @@
};
return ShortcutsDashboardNavigation;
-
})(Shortcuts);
-
}).call(this);
diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js
index 3a81380eef0..8469837533b 100644
--- a/app/assets/javascripts/shortcuts_find_file.js
+++ b/app/assets/javascripts/shortcuts_find_file.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife */
/* global Mousetrap */
/* global ShortcutsNavigation */
@@ -34,7 +34,5 @@
}
return ShortcutsFindFile;
-
})(ShortcutsNavigation);
-
}).call(this);
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index b892fbc3393..4ef516af8c8 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, one-var-declaration-per-line, quotes, prefer-arrow-callback, consistent-return, prefer-template, no-mixed-operators, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, one-var-declaration-per-line, quotes, prefer-arrow-callback, consistent-return, prefer-template, no-mixed-operators */
/* global Mousetrap */
/* global Turbolinks */
/* global ShortcutsNavigation */
@@ -39,29 +39,39 @@
}
ShortcutsIssuable.prototype.replyWithSelectedText = function() {
- var quote, replyField, selected, separator;
- if (window.getSelection) {
- selected = window.getSelection().toString();
- replyField = $('.js-main-target-form #note_note');
- if (selected.trim() === "") {
- return;
- }
- // Put a '>' character before each non-empty line in the selection
- quote = _.map(selected.split("\n"), function(val) {
- if (val.trim() !== '') {
- return "> " + val + "\n";
- }
- });
- // If replyField already has some content, add a newline before our quote
- separator = replyField.val().trim() !== "" && "\n" || '';
- replyField.val(function(_, current) {
- return current + separator + quote.join('') + "\n";
- });
- // Trigger autosave for the added text
- replyField.trigger('input');
- // Focus the input field
- return replyField.focus();
+ var quote, replyField, documentFragment, selected, separator;
+
+ documentFragment = window.gl.utils.getSelectedFragment();
+ if (!documentFragment) return;
+
+ // If the documentFragment contains more than just Markdown, don't copy as GFM.
+ if (documentFragment.querySelector('.md, .wiki')) return;
+
+ selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
+
+ replyField = $('.js-main-target-form #note_note');
+ if (selected.trim() === "") {
+ return;
}
+ quote = _.map(selected.split("\n"), function(val) {
+ return ("> " + val).trim() + "\n";
+ });
+ // If replyField already has some content, add a newline before our quote
+ separator = replyField.val().trim() !== "" && "\n\n" || '';
+ replyField.val(function(_, current) {
+ return current + separator + quote.join('') + "\n";
+ });
+
+ // Trigger autosave
+ replyField.trigger('input');
+
+ // Trigger autosize
+ var event = document.createEvent('Event');
+ event.initEvent('autosize:update', true, false);
+ replyField.get(0).dispatchEvent(event);
+
+ // Focus the input field
+ return replyField.focus();
};
ShortcutsIssuable.prototype.editIssue = function() {
@@ -76,7 +86,5 @@
};
return ShortcutsIssuable;
-
})(ShortcutsNavigation);
-
}).call(this);
diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js
index 0776d0a9b67..afeda0dd5fe 100644
--- a/app/assets/javascripts/shortcuts_navigation.js
+++ b/app/assets/javascripts/shortcuts_navigation.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */
/* global Shortcuts */
@@ -64,7 +64,5 @@
};
return ShortcutsNavigation;
-
})(Shortcuts);
-
}).call(this);
diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/shortcuts_network.js
index ecc3fab84c3..79896e35cbb 100644
--- a/app/assets/javascripts/shortcuts_network.js
+++ b/app/assets/javascripts/shortcuts_network.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, max-len */
/* global Mousetrap */
/* global ShortcutsNavigation */
@@ -24,7 +24,5 @@
}
return ShortcutsNetwork;
-
})(ShortcutsNavigation);
-
}).call(this);
diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6
index 9790a44972d..05234643c18 100644
--- a/app/assets/javascripts/sidebar.js.es6
+++ b/app/assets/javascripts/sidebar.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable arrow-parens, class-methods-use-this, no-param-reassign, padded-blocks */
+/* eslint-disable arrow-parens, class-methods-use-this, no-param-reassign */
/* global Cookies */
((global) => {
@@ -94,5 +94,4 @@
}
global.Sidebar = Sidebar;
-
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js
index ac8603ccd10..5b20c63384c 100644
--- a/app/assets/javascripts/single_file_diff.js
+++ b/app/assets/javascripts/single_file_diff.js
@@ -1,7 +1,7 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, padded-blocks, max-len */
+/* eslint-disable func-names, prefer-arrow-callback, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, one-var-declaration-per-line, consistent-return, no-param-reassign, max-len */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
@@ -14,8 +14,7 @@
COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>';
- function SingleFileDiff(file, forceLoad, cb) {
- var clickTarget;
+ function SingleFileDiff(file) {
this.file = file;
this.toggleDiff = bind(this.toggleDiff, this);
this.content = $('.diff-content', this.file);
@@ -33,14 +32,13 @@
this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down');
}
- clickTarget = $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff);
- if (forceLoad) {
- this.toggleDiff({ target: clickTarget }, cb);
- }
+
+ $('.file-title, .click-to-expand', this.file).on('click', (function (e) {
+ this.toggleDiff($(e.target));
+ }).bind(this));
}
- SingleFileDiff.prototype.toggleDiff = function(e, cb) {
- var $target = $(e.target);
+ SingleFileDiff.prototype.toggleDiff = function($target, cb) {
if (!$target.hasClass('file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return;
this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) {
@@ -88,15 +86,13 @@
};
return SingleFileDiff;
-
})();
- $.fn.singleFileDiff = function(forceLoad, cb) {
+ $.fn.singleFileDiff = function() {
return this.each(function() {
- if (!$.data(this, 'singleFileDiff') || forceLoad) {
- return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this, forceLoad, cb));
+ if (!$.data(this, 'singleFileDiff')) {
+ return $.data(this, 'singleFileDiff', new window.SingleFileDiff(this));
}
});
};
-
}).call(this);
diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js
index 18512d179b3..cfb4ff82a73 100644
--- a/app/assets/javascripts/snippet/snippet_bundle.js
+++ b/app/assets/javascripts/snippet/snippet_bundle.js
@@ -1,15 +1,14 @@
-/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, semi, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, max-len */
/* global ace */
/*= require_tree . */
(function() {
$(function() {
- var editor = ace.edit("editor")
+ var editor = ace.edit("editor");
$(".snippet-form-holder form").on('submit', function() {
$(".snippet-file-content").val(editor.getValue());
});
});
-
}).call(this);
diff --git a/app/assets/javascripts/snippets_list.js.es6 b/app/assets/javascripts/snippets_list.js.es6
index 6f913326a3a..2128007113f 100644
--- a/app/assets/javascripts/snippets_list.js.es6
+++ b/app/assets/javascripts/snippets_list.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, semi, max-len */
+/* eslint-disable arrow-parens, no-param-reassign, space-before-function-paren, func-names, no-var, max-len */
(global => {
global.gl = global.gl || {};
@@ -9,5 +9,5 @@
$holder.find('.pagination').on('ajax:success', (e, data) => {
$holder.replaceWith(data.html);
});
- }
+ };
})(window);
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index f1fc526bf2e..531fd0e9c32 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
/* global Flash */
(function() {
@@ -26,7 +26,5 @@
}
return Star;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/subbable_resource.js.es6 b/app/assets/javascripts/subbable_resource.js.es6
index 932120157a3..d8191605128 100644
--- a/app/assets/javascripts/subbable_resource.js.es6
+++ b/app/assets/javascripts/subbable_resource.js.es6
@@ -1,6 +1,3 @@
-//= require vue
-//= require vue-resource
-
(() => {
/*
* SubbableResource can be extended to provide a pubsub-style service for one-off REST
diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js
index 185d20775d0..187356f0bf9 100644
--- a/app/assets/javascripts/subscription_select.js
+++ b/app/assets/javascripts/subscription_select.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, object-shorthand, no-unused-vars, no-shadow, one-var, one-var-declaration-per-line, comma-dangle, max-len */
(function() {
this.SubscriptionSelect = (function() {
function SubscriptionSelect() {
@@ -30,7 +30,5 @@
}
return SubscriptionSelect;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js
index 5d0fa62c50a..115716bff6a 100644
--- a/app/assets/javascripts/syntax_highlight.js
+++ b/app/assets/javascripts/syntax_highlight.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, consistent-return, no-var, no-else-return, prefer-arrow-callback, max-len */
// Syntax Highlighter
//
@@ -10,7 +10,6 @@
// <div class="js-syntax-highlight"></div>
//
(function() {
-
$.fn.syntaxHighlight = function() {
var $children;
@@ -25,5 +24,4 @@
}
}
};
-
}).call(this);
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js.es6
index d2b152045b4..b0132af70f2 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js.es6
+++ b/app/assets/javascripts/templates/issuable_template_selector.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable prefer-const, comma-dangle, max-len, no-useless-return, object-curly-spacing, no-param-reassign, max-len */
+/* eslint-disable comma-dangle, max-len, no-useless-return, no-param-reassign, max-len */
/* global Api */
/*= require ../blob/template_selector */
@@ -12,7 +12,7 @@
this.issuableType = this.wrapper.data('issuable-type');
this.titleInput = $(`#${this.issuableType}_title`);
- let initialQuery = {
+ const initialQuery = {
name: this.dropdown.data('selected')
};
@@ -23,7 +23,7 @@
});
$('.no-template', this.dropdown.parent()).on('click', () => {
- this.currentTemplate = '';
+ this.currentTemplate.content = '';
this.setInputValueToTemplateContent();
$('.dropdown-toggle-text', this.dropdown).text('Choose a template');
});
@@ -47,10 +47,10 @@
// If the title has not yet been set, focus the title input and
// skip focusing the description input by setting `true` as the
// `skipFocus` option to `requestFileSuccess`.
- this.requestFileSuccess(this.currentTemplate, {skipFocus: true});
+ this.requestFileSuccess(this.currentTemplate, { skipFocus: true });
this.titleInput.focus();
} else {
- this.requestFileSuccess(this.currentTemplate, {skipFocus: false});
+ this.requestFileSuccess(this.currentTemplate, { skipFocus: false });
}
return;
}
diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 b/app/assets/javascripts/templates/issuable_template_selectors.js.es6
index 7310b9de074..97f6d37364d 100644
--- a/app/assets/javascripts/templates/issuable_template_selectors.js.es6
+++ b/app/assets/javascripts/templates/issuable_template_selectors.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable no-new, comma-dangle, class-methods-use-this, prefer-const, no-param-reassign */
+/* eslint-disable no-new, comma-dangle, class-methods-use-this, no-param-reassign */
((global) => {
class IssuableTemplateSelectors {
@@ -19,7 +19,7 @@
}
initEditor() {
- let editor = $('.markdown-area');
+ const editor = $('.markdown-area');
// Proxy ace-editor's .setValue to jQuery's .val
editor.setValue = editor.val;
editor.getValue = editor.val;
diff --git a/app/assets/javascripts/terminal/terminal_bundle.js.es6 b/app/assets/javascripts/terminal/terminal_bundle.js.es6
index ded7ee6e9fe..33d2c1e1a17 100644
--- a/app/assets/javascripts/terminal/terminal_bundle.js.es6
+++ b/app/assets/javascripts/terminal/terminal_bundle.js.es6
@@ -1,3 +1,5 @@
+//= require xterm/encoding-indexes
+//= require xterm/encoding
//= require xterm/xterm.js
//= require xterm/fit.js
//= require ./terminal.js
diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6
index d8713600030..ef9c0a885fb 100644
--- a/app/assets/javascripts/todos.js.es6
+++ b/app/assets/javascripts/todos.js.es6
@@ -1,9 +1,8 @@
-/* eslint-disable padded-blocks, class-methods-use-this, no-new, func-names, prefer-template, no-unneeded-ternary, object-shorthand, space-before-function-paren, comma-dangle, quote-props, consistent-return, no-else-return, semi, no-param-reassign, max-len */
+/* eslint-disable class-methods-use-this, no-new, func-names, prefer-template, no-unneeded-ternary, object-shorthand, space-before-function-paren, comma-dangle, quote-props, consistent-return, no-else-return, no-param-reassign, max-len */
/* global UsersSelect */
/* global Turbolinks */
((global) => {
-
class Todos {
constructor({ el } = {}) {
this.allDoneClicked = this.allDoneClicked.bind(this);
@@ -49,7 +48,7 @@
clicked: function() {
return $dropdown.closest('form.filter-form').submit();
}
- })
+ });
}
doneClicked(e) {
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
index f48a7ee0f55..d124ca4f88b 100644
--- a/app/assets/javascripts/tree.js
+++ b/app/assets/javascripts/tree.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, max-len */
/* global Turbolinks */
(function() {
this.TreeView = (function() {
@@ -64,7 +64,5 @@
};
return TreeView;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/u2f/authenticate.js.es6 b/app/assets/javascripts/u2f/authenticate.js.es6
index 2b992109a8c..500b78fc5d8 100644
--- a/app/assets/javascripts/u2f/authenticate.js.es6
+++ b/app/assets/javascripts/u2f/authenticate.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, prefer-arrow-callback, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, prefer-arrow-callback, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, max-len */
/* global u2f */
/* global U2FError */
/* global U2FUtil */
@@ -10,7 +10,7 @@
(function() {
const global = window.gl || (window.gl = {});
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
global.U2FAuthenticate = (function() {
function U2FAuthenticate(container, form, u2fParams, fallbackButton, fallbackUI) {
@@ -57,7 +57,7 @@
return function(response) {
var error;
if (response.errorCode) {
- error = new U2FError(response.errorCode);
+ error = new U2FError(response.errorCode, 'authenticate');
return _this.renderError(error);
} else {
return _this.renderAuthenticated(JSON.stringify(response));
@@ -114,7 +114,5 @@
};
return U2FAuthenticate;
-
})();
-
})();
diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js
index bb9942a3aa0..86b459e1866 100644
--- a/app/assets/javascripts/u2f/error.js
+++ b/app/assets/javascripts/u2f/error.js
@@ -1,29 +1,27 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-console, quotes, prefer-template, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-console, quotes, prefer-template, max-len */
/* global u2f */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.U2FError = (function() {
- function U2FError(errorCode) {
+ function U2FError(errorCode, u2fFlowType) {
this.errorCode = errorCode;
this.message = bind(this.message, this);
this.httpsDisabled = window.location.protocol !== 'https:';
+ this.u2fFlowType = u2fFlowType;
}
U2FError.prototype.message = function() {
- switch (false) {
- case !(this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled):
- return "U2F only works with HTTPS-enabled websites. Contact your administrator for more details.";
- case this.errorCode !== u2f.ErrorCodes.DEVICE_INELIGIBLE:
- return "This device has already been registered with us.";
- default:
- return "There was a problem communicating with your device.";
+ if (this.errorCode === u2f.ErrorCodes.BAD_REQUEST && this.httpsDisabled) {
+ return 'U2F only works with HTTPS-enabled websites. Contact your administrator for more details.';
+ } else if (this.errorCode === u2f.ErrorCodes.DEVICE_INELIGIBLE) {
+ if (this.u2fFlowType === 'authenticate') return 'This device has not been registered with us.';
+ if (this.u2fFlowType === 'register') return 'This device has already been registered with us.';
}
+ return "There was a problem communicating with your device.";
};
return U2FError;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js
index 050c9bfc02e..69d1ff3a39e 100644
--- a/app/assets/javascripts/u2f/register.js
+++ b/app/assets/javascripts/u2f/register.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-else-return, quotes, quote-props, comma-dangle, one-var, one-var-declaration-per-line, max-len */
/* global u2f */
/* global U2FError */
/* global U2FUtil */
@@ -8,7 +8,7 @@
// State Flow #1: setup -> in_progress -> registered -> POST to server
// State Flow #2: setup -> in_progress -> error -> setup
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.U2FRegister = (function() {
function U2FRegister(container, u2fParams) {
@@ -39,7 +39,7 @@
return function(response) {
var error;
if (response.errorCode) {
- error = new U2FError(response.errorCode);
+ error = new U2FError(response.errorCode, 'register');
return _this.renderError(error);
} else {
return _this.renderRegistered(JSON.stringify(response));
@@ -94,7 +94,5 @@
};
return U2FRegister;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js
index eedd3bcd5a1..34e88220b12 100644
--- a/app/assets/javascripts/u2f/util.js
+++ b/app/assets/javascripts/u2f/util.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife */
(function() {
this.U2FUtil = (function() {
function U2FUtil() {}
@@ -8,7 +8,5 @@
};
return U2FUtil;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6
index 0a2db7c05fe..059e6c628b3 100644
--- a/app/assets/javascripts/user.js.es6
+++ b/app/assets/javascripts/user.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign, semi */
+/* eslint-disable class-methods-use-this, comma-dangle, arrow-parens, no-param-reassign */
/* global Cookies */
((global) => {
@@ -30,5 +30,5 @@
$(this).parents('.project-limit-message').remove();
});
}
- }
+ };
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js.es6
index b9c23b51b4d..313fb17aee8 100644
--- a/app/assets/javascripts/user_tabs.js.es6
+++ b/app/assets/javascripts/user_tabs.js.es6
@@ -1,4 +1,4 @@
-/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, array-bracket-spacing, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, semi, no-param-reassign */
+/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign */
/*
UserTabs
@@ -107,7 +107,7 @@ content on the Users#show page.
this.loadActivities(source);
}
- const loadableActions = [ 'groups', 'contributed', 'projects', 'snippets' ];
+ const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
if (loadableActions.indexOf(action) > -1) {
return this.loadTab(source, action);
}
@@ -145,7 +145,7 @@ content on the Users#show page.
}
setCurrentAction(source, action) {
- let new_state = source
+ let new_state = source;
new_state = new_state.replace(/\/+$/, '');
new_state += this._location.search + this._location.hash;
history.replaceState({
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
index 578be7c3590..e7280d643d3 100644
--- a/app/assets/javascripts/users/calendar.js
+++ b/app/assets/javascripts/users/calendar.js
@@ -1,9 +1,9 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, camelcase, vars-on-top, semi, keyword-spacing, no-plusplus, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */
/* global d3 */
/* global dateFormat */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.Calendar = (function() {
function Calendar(timestamps, calendar_activities_path) {
@@ -20,7 +20,7 @@
this.timestampsTmp = [];
var group = 0;
- var today = new Date()
+ var today = new Date();
today.setHours(0, 0, 0, 0, 0);
var oneYearAgo = new Date(today);
@@ -28,7 +28,7 @@
var days = gl.utils.getDayDifference(oneYearAgo, today);
- for(var i = 0; i <= days; i++) {
+ for (var i = 0; i <= days; i += 1) {
var date = new Date(oneYearAgo);
date.setDate(date.getDate() + i);
@@ -39,7 +39,7 @@
// or if is first object
if ((day === 0 && i !== 0) || i === 0) {
this.timestampsTmp.push([]);
- group++;
+ group += 1;
}
var innerArray = this.timestampsTmp[group - 1];
@@ -74,7 +74,7 @@
}
return extraWidthPadding;
- }
+ };
Calendar.prototype.renderSvg = function(group) {
var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
@@ -158,7 +158,7 @@
};
Calendar.prototype.renderMonths = function() {
- return this.svg.append('g').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
+ return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
return date.x;
}).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
return function(date) {
@@ -221,7 +221,5 @@
};
return Calendar;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index d4b5e03aa35..77d2764cdf0 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -1,10 +1,10 @@
-/* eslint-disable func-names, space-before-function-paren, one-var, no-var, space-before-blocks, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-plusplus, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, keyword-spacing, no-param-reassign, padded-blocks */
+/* eslint-disable func-names, space-before-function-paren, one-var, no-var, prefer-rest-params, wrap-iife, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, no-lonely-if, yoda, prefer-spread, no-void, camelcase, no-param-reassign */
/* global Vue */
/* global Issuable */
/* global ListUser */
(function() {
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
+ var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
slice = [].slice;
this.UsersSelect = (function() {
@@ -116,7 +116,7 @@
showDivider = 0;
if (firstUser) {
// Move current user to the front of the list
- for (index = j = 0, len = users.length; j < len; index = ++j) {
+ for (index = j = 0, len = users.length; j < len; index = (j += 1)) {
obj = users[index];
if (obj.username === firstUser) {
users.splice(index, 1);
@@ -278,7 +278,7 @@
if (firstUser) {
// Move current user to the front of the list
ref = data.results;
- for (index = j = 0, len = ref.length; j < len; index = ++j) {
+ for (index = j = 0, len = ref.length; j < len; index = (j += 1)) {
obj = ref[index];
if (obj.username === firstUser) {
data.results.splice(index, 1);
@@ -371,7 +371,7 @@
};
UsersSelect.prototype.user = function(user_id, callback) {
- if(!/^\d+$/.test(user_id)) {
+ if (!/^\d+$/.test(user_id)) {
return false;
}
@@ -421,7 +421,5 @@
};
return UsersSelect;
-
})();
-
}).call(this);
diff --git a/app/assets/javascripts/version_check_image.js.es6 b/app/assets/javascripts/version_check_image.js.es6
new file mode 100644
index 00000000000..1fa2b5ac399
--- /dev/null
+++ b/app/assets/javascripts/version_check_image.js.es6
@@ -0,0 +1,10 @@
+(() => {
+ class VersionCheckImage {
+ static bindErrorEvent(imageElement) {
+ imageElement.off('error').on('error', () => imageElement.hide());
+ }
+ }
+
+ window.gl = window.gl || {};
+ gl.VersionCheckImage = VersionCheckImage;
+})();
diff --git a/app/assets/javascripts/visibility_select.js.es6 b/app/assets/javascripts/visibility_select.js.es6
new file mode 100644
index 00000000000..f712d7ba930
--- /dev/null
+++ b/app/assets/javascripts/visibility_select.js.es6
@@ -0,0 +1,27 @@
+(() => {
+ const gl = window.gl || (window.gl = {});
+
+ class VisibilitySelect {
+ constructor(container) {
+ if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
+ this.container = container;
+ this.helpBlock = this.container.querySelector('.help-block');
+ this.select = this.container.querySelector('select');
+ }
+
+ init() {
+ if (this.select) {
+ this.updateHelpText();
+ this.select.addEventListener('change', this.updateHelpText.bind(this));
+ } else {
+ this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
+ }
+ }
+
+ updateHelpText() {
+ this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
+ }
+ }
+
+ gl.VisibilitySelect = VisibilitySelect;
+})();
diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_pagination/index.js.es6
new file mode 100644
index 00000000000..605824fa939
--- /dev/null
+++ b/app/assets/javascripts/vue_pagination/index.js.es6
@@ -0,0 +1,148 @@
+/* global Vue, gl */
+/* eslint-disable no-param-reassign, no-plusplus */
+
+((gl) => {
+ const PAGINATION_UI_BUTTON_LIMIT = 4;
+ const UI_LIMIT = 6;
+ const SPREAD = '...';
+ const PREV = 'Prev';
+ const NEXT = 'Next';
+ const FIRST = '<< First';
+ const LAST = 'Last >>';
+
+ gl.VueGlPagination = Vue.extend({
+ props: {
+
+ /**
+ This function will take the information given by the pagination component
+ And make a new Turbolinks call
+
+ Here is an example `change` method:
+
+ change(pagenum, apiScope) {
+ Turbolinks.visit(`?scope=${apiScope}&p=${pagenum}`);
+ },
+ */
+
+ change: {
+ type: Function,
+ required: true,
+ },
+
+ /**
+ pageInfo will come from the headers of the API call
+ in the `.then` clause of the VueResource API call
+ there should be a function that contructs the pageInfo for this component
+
+ This is an example:
+
+ const pageInfo = headers => ({
+ perPage: +headers['X-Per-Page'],
+ page: +headers['X-Page'],
+ total: +headers['X-Total'],
+ totalPages: +headers['X-Total-Pages'],
+ nextPage: +headers['X-Next-Page'],
+ previousPage: +headers['X-Prev-Page'],
+ });
+ */
+
+ pageInfo: {
+ type: Object,
+ required: true,
+ },
+ },
+ methods: {
+ changePage(e) {
+ let apiScope = gl.utils.getParameterByName('scope');
+
+ if (!apiScope) apiScope = 'all';
+
+ const text = e.target.innerText;
+ const { totalPages, nextPage, previousPage } = this.pageInfo;
+
+ switch (text) {
+ case SPREAD:
+ break;
+ case LAST:
+ this.change(totalPages, apiScope);
+ break;
+ case NEXT:
+ this.change(nextPage, apiScope);
+ break;
+ case PREV:
+ this.change(previousPage, apiScope);
+ break;
+ case FIRST:
+ this.change(1, apiScope);
+ break;
+ default:
+ this.change(+text, apiScope);
+ break;
+ }
+ },
+ },
+ computed: {
+ prev() {
+ return this.pageInfo.previousPage;
+ },
+ next() {
+ return this.pageInfo.nextPage;
+ },
+ getItems() {
+ const total = this.pageInfo.totalPages;
+ const page = this.pageInfo.page;
+ const items = [];
+
+ if (page > 1) items.push({ title: FIRST });
+
+ 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++) {
+ 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;
+ },
+ },
+ template: `
+ <div class="gl-pagination">
+ <ul class="pagination clearfix">
+ <li v-for='item in getItems'
+ :class='{
+ page: item.page,
+ prev: item.prev,
+ next: item.next,
+ separator: item.separator,
+ active: item.active,
+ disabled: item.disabled
+ }'
+ >
+ <a @click="changePage($event)">{{item.title}}</a>
+ </li>
+ </ul>
+ </div>
+ `,
+ });
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js.es6
new file mode 100644
index 00000000000..edd01f17a97
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/index.js.es6
@@ -0,0 +1,42 @@
+/* global Vue, VueResource, gl */
+/*= require vue_common_component/commit */
+/*= require vue_pagination/index */
+/*= require vue-resource
+/*= require boards/vue_resource_interceptor */
+/*= require ./status.js.es6 */
+/*= require ./store.js.es6 */
+/*= require ./pipeline_url.js.es6 */
+/*= require ./stage.js.es6 */
+/*= require ./stages.js.es6 */
+/*= require ./pipeline_actions.js.es6 */
+/*= require ./time_ago.js.es6 */
+/*= require ./pipelines.js.es6 */
+
+(() => {
+ const project = document.querySelector('.pipelines');
+ const entry = document.querySelector('.vue-pipelines-index');
+ const svgs = document.querySelector('.pipeline-svgs');
+
+ Vue.use(VueResource);
+
+ if (!entry) return null;
+ return new Vue({
+ el: entry,
+ data: {
+ scope: project.dataset.url,
+ store: new gl.PipelineStore(),
+ svgs: svgs.dataset,
+ },
+ components: {
+ 'vue-pipelines': gl.VuePipelines,
+ },
+ template: `
+ <vue-pipelines
+ :scope='scope'
+ :store='store'
+ :svgs='svgs'
+ >
+ </vue-pipelines>
+ `,
+ });
+})();
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
new file mode 100644
index 00000000000..b195b0ef3ba
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
@@ -0,0 +1,108 @@
+/* global Vue, Flash, gl */
+/* eslint-disable no-param-reassign */
+
+((gl) => {
+ gl.VuePipelineActions = Vue.extend({
+ props: ['pipeline', 'svgs'],
+ computed: {
+ actions() {
+ return this.pipeline.details.manual_actions.length > 0;
+ },
+ artifacts() {
+ return this.pipeline.details.artifacts.length > 0;
+ },
+ },
+ methods: {
+ download(name) {
+ return `Download ${name} artifacts`;
+ },
+ },
+ template: `
+ <td class="pipeline-actions hidden-xs">
+ <div class="controls pull-right">
+ <div class="btn-group inline">
+ <div class="btn-group">
+ <button
+ v-if='actions'
+ class="dropdown-toggle btn btn-default has-tooltip js-pipeline-dropdown-manual-actions"
+ data-toggle="dropdown"
+ title="Manual build"
+ data-placement="top"
+ data-toggle="dropdown"
+ aria-label="Manual build"
+ >
+ <span v-html='svgs.iconPlay' aria-hidden="true"></span>
+ <i class="fa fa-caret-down" aria-hidden="true"></i>
+ </button>
+ <ul class="dropdown-menu dropdown-menu-align-right">
+ <li v-for='action in pipeline.details.manual_actions'>
+ <a
+ rel="nofollow"
+ data-method="post"
+ :href='action.path'
+ >
+ <span v-html='svgs.iconPlay' aria-hidden="true"></span>
+ <span>{{action.name}}</span>
+ </a>
+ </li>
+ </ul>
+ </div>
+ <div class="btn-group">
+ <button
+ v-if='artifacts'
+ class="dropdown-toggle btn btn-default build-artifacts has-tooltip js-pipeline-dropdown-download"
+ data-toggle="dropdown"
+ title="Artifacts"
+ data-placement="top"
+ data-toggle="dropdown"
+ aria-label="Artifacts"
+ >
+ <i class="fa fa-download" aria-hidden="true"></i>
+ <i class="fa fa-caret-down" aria-hidden="true"></i>
+ </button>
+ <ul class="dropdown-menu dropdown-menu-align-right">
+ <li v-for='artifact in pipeline.details.artifacts'>
+ <a
+ rel="nofollow"
+ :href='artifact.path'
+ >
+ <i class="fa fa-download" aria-hidden="true"></i>
+ <span>{{download(artifact.name)}}</span>
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="cancel-retry-btns inline">
+ <a
+ v-if='pipeline.flags.retryable'
+ class="btn has-tooltip"
+ title="Retry"
+ rel="nofollow"
+ data-method="post"
+ data-placement="top"
+ data-toggle="dropdown"
+ :href='pipeline.retry_path'
+ aria-label="Retry"
+ >
+ <i class="fa fa-repeat" aria-hidden="true"></i>
+ </a>
+ <a
+ v-if='pipeline.flags.cancelable'
+ class="btn btn-remove has-tooltip"
+ title="Cancel"
+ rel="nofollow"
+ data-method="post"
+ data-placement="top"
+ data-toggle="dropdown"
+ :href='pipeline.cancel_path'
+ aria-label="Cancel"
+ >
+ <i class="fa fa-remove" aria-hidden="true"></i>
+ </a>
+ </div>
+ </div>
+ </td>
+ `,
+ });
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6
new file mode 100644
index 00000000000..ae5649f0519
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6
@@ -0,0 +1,63 @@
+/* global Vue, gl */
+/* eslint-disable no-param-reassign */
+
+((gl) => {
+ gl.VuePipelineUrl = Vue.extend({
+ props: [
+ 'pipeline',
+ ],
+ computed: {
+ user() {
+ return !!this.pipeline.user;
+ },
+ },
+ template: `
+ <td>
+ <a :href='pipeline.path'>
+ <span class="pipeline-id">#{{pipeline.id}}</span>
+ </a>
+ <span>by</span>
+ <a
+ v-if='user'
+ :href='pipeline.user.web_url'
+ >
+ <img
+ v-if='user'
+ class="avatar has-tooltip s20 "
+ :title='pipeline.user.name'
+ data-container="body"
+ :src='pipeline.user.avatar_url'
+ >
+ </a>
+ <span
+ v-if='!user'
+ class="api monospace"
+ >
+ API
+ </span>
+ <span
+ v-if='pipeline.flags.latest'
+ class="label label-success has-tooltip"
+ title="Latest pipeline for this branch"
+ data-original-title="Latest pipeline for this branch"
+ >
+ latest
+ </span>
+ <span
+ v-if='pipeline.flags.yaml_errors'
+ class="label label-danger has-tooltip"
+ :title='pipeline.yaml_errors'
+ :data-original-title='pipeline.yaml_errors'
+ >
+ yaml invalid
+ </span>
+ <span
+ v-if='pipeline.flags.stuck'
+ class="label label-warning"
+ >
+ stuck
+ </span>
+ </td>
+ `,
+ });
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
new file mode 100644
index 00000000000..b2ed05503c9
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
@@ -0,0 +1,131 @@
+/* global Vue, Turbolinks, gl */
+/* eslint-disable no-param-reassign */
+
+((gl) => {
+ gl.VuePipelines = Vue.extend({
+ components: {
+ runningPipeline: gl.VueRunningPipeline,
+ pipelineActions: gl.VuePipelineActions,
+ stages: gl.VueStages,
+ commit: gl.CommitComponent,
+ pipelineUrl: gl.VuePipelineUrl,
+ pipelineHead: gl.VuePipelineHead,
+ glPagination: gl.VueGlPagination,
+ statusScope: gl.VueStatusScope,
+ timeAgo: gl.VueTimeAgo,
+ },
+ data() {
+ return {
+ pipelines: [],
+ timeLoopInterval: '',
+ intervalId: '',
+ apiScope: 'all',
+ pageInfo: {},
+ pagenum: 1,
+ count: { all: 0, running_or_pending: 0 },
+ pageRequest: false,
+ };
+ },
+ props: ['scope', 'store', 'svgs'],
+ created() {
+ const pagenum = gl.utils.getParameterByName('p');
+ const scope = gl.utils.getParameterByName('scope');
+ if (pagenum) this.pagenum = pagenum;
+ if (scope) this.apiScope = scope;
+ this.store.fetchDataLoop.call(this, Vue, this.pagenum, this.scope, this.apiScope);
+ },
+ methods: {
+ change(pagenum, apiScope) {
+ Turbolinks.visit(`?scope=${apiScope}&p=${pagenum}`);
+ },
+ author(pipeline) {
+ if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' };
+ if (pipeline.commit.author) return pipeline.commit.author;
+ return {
+ avatar_url: pipeline.commit.author_gravatar_url,
+ web_url: `mailto:${pipeline.commit.author_email}`,
+ username: pipeline.commit.author_name,
+ };
+ },
+ ref(pipeline) {
+ const { ref } = pipeline;
+ return { name: ref.name, tag: ref.tag, ref_url: ref.path };
+ },
+ commitTitle(pipeline) {
+ return pipeline.commit ? pipeline.commit.title : '';
+ },
+ commitSha(pipeline) {
+ return pipeline.commit ? pipeline.commit.short_id : '';
+ },
+ commitUrl(pipeline) {
+ return pipeline.commit ? pipeline.commit.commit_path : '';
+ },
+ match(string) {
+ return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase());
+ },
+ },
+ template: `
+ <div>
+ <div class="pipelines realtime-loading" v-if='pipelines.length < 1'>
+ <i class="fa fa-spinner fa-spin"></i>
+ </div>
+ <div class="table-holder" v-if='pipelines.length'>
+ <table class="table ci-table">
+ <thead>
+ <tr>
+ <th class="pipeline-status">Status</th>
+ <th class="pipeline-info">Pipeline</th>
+ <th class="pipeline-commit">Commit</th>
+ <th class="pipeline-stages">Stages</th>
+ <th class="pipeline-date"></th>
+ <th class="pipeline-actions hidden-xs"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="commit" v-for='pipeline in pipelines'>
+ <status-scope
+ :pipeline='pipeline'
+ :match='match'
+ :svgs='svgs'
+ >
+ </status-scope>
+ <pipeline-url :pipeline='pipeline'></pipeline-url>
+ <td>
+ <commit
+ :commit-icon-svg='svgs.commitIconSvg'
+ :author='author(pipeline)'
+ :tag="pipeline.ref.tag"
+ :title='commitTitle(pipeline)'
+ :commit-ref='ref(pipeline)'
+ :short-sha='commitSha(pipeline)'
+ :commit-url='commitUrl(pipeline)'
+ >
+ </commit>
+ </td>
+ <stages
+ :pipeline='pipeline'
+ :svgs='svgs'
+ :match='match'
+ >
+ </stages>
+ <time-ago :pipeline='pipeline' :svgs='svgs'></time-ago>
+ <pipeline-actions :pipeline='pipeline' :svgs='svgs'></pipeline-actions>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <div class="pipelines realtime-loading" v-if='pageRequest'>
+ <i class="fa fa-spinner fa-spin"></i>
+ </div>
+ <gl-pagination
+ v-if='pageInfo.total > pageInfo.perPage'
+ :pagenum='pagenum'
+ :change='change'
+ :count='count.all'
+ :pageInfo='pageInfo'
+ >
+ </gl-pagination>
+ </div>
+ `,
+ });
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js.es6
new file mode 100644
index 00000000000..496df9aaced
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/stage.js.es6
@@ -0,0 +1,103 @@
+/* global Vue, Flash, gl */
+/* eslint-disable no-param-reassign */
+
+((gl) => {
+ gl.VueStage = Vue.extend({
+ data() {
+ return {
+ builds: '',
+ spinner: '<span class="fa fa-spinner fa-spin"></span>',
+ };
+ },
+ props: {
+ stage: {
+ type: Object,
+ required: true,
+ },
+ svgs: {
+ type: DOMStringMap,
+ required: true,
+ },
+ match: {
+ type: Function,
+ required: true,
+ },
+ },
+ methods: {
+ fetchBuilds(e) {
+ const areaExpanded = e.currentTarget.attributes['aria-expanded'];
+
+ if (areaExpanded && (areaExpanded.textContent === 'true')) return null;
+
+ return this.$http.get(this.stage.dropdown_path)
+ .then((response) => {
+ this.builds = JSON.parse(response.body).html;
+ }, () => {
+ const flash = new Flash('Something went wrong on our end.');
+ return flash;
+ });
+ },
+ keepGraph(e) {
+ const { target } = e;
+
+ if (target.className.indexOf('js-ci-action-icon') >= 0) return null;
+
+ if (
+ target.parentElement &&
+ (target.parentElement.className.indexOf('js-ci-action-icon') >= 0)
+ ) return null;
+
+ return e.stopPropagation();
+ },
+ },
+ computed: {
+ buildsOrSpinner() {
+ return this.builds ? this.builds : this.spinner;
+ },
+ dropdownClass() {
+ if (this.builds) return 'js-builds-dropdown-container';
+ return 'js-builds-dropdown-loading builds-dropdown-loading';
+ },
+ buildStatus() {
+ return `Build: ${this.stage.status.label}`;
+ },
+ tooltip() {
+ return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`;
+ },
+ svg() {
+ const { icon } = this.stage.status;
+ const stageIcon = icon.replace(/icon/i, 'stage_icon');
+ return this.svgs[this.match(stageIcon)];
+ },
+ triggerButtonClass() {
+ return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
+ },
+ },
+ template: `
+ <div>
+ <button
+ @click='fetchBuilds($event)'
+ :class="triggerButtonClass"
+ :title='stage.title'
+ data-placement="top"
+ data-toggle="dropdown"
+ type="button"
+ :aria-label='stage.title'
+ >
+ <span v-html="svg" aria-hidden="true"></span>
+ <i class="fa fa-caret-down" aria-hidden="true"></i>
+ </button>
+ <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
+ <div class="arrow-up" aria-hidden="true"></div>
+ <div
+ @click='keepGraph($event)'
+ :class="dropdownClass"
+ class="js-builds-dropdown-list scrollable-menu"
+ v-html="buildsOrSpinner"
+ >
+ </div>
+ </ul>
+ </div>
+ `,
+ });
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/stages.js.es6 b/app/assets/javascripts/vue_pipelines_index/stages.js.es6
new file mode 100644
index 00000000000..cb176b3f0c6
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/stages.js.es6
@@ -0,0 +1,21 @@
+/* global Vue, gl */
+/* eslint-disable no-param-reassign */
+
+((gl) => {
+ gl.VueStages = Vue.extend({
+ components: {
+ 'vue-stage': gl.VueStage,
+ },
+ props: ['pipeline', 'svgs', 'match'],
+ template: `
+ <td class="stage-cell">
+ <div
+ class="stage-container dropdown js-mini-pipeline-graph"
+ v-for='stage in pipeline.details.stages'
+ >
+ <vue-stage :stage='stage' :svgs='svgs' :match='match'></vue-stage>
+ </div>
+ </td>
+ `,
+ });
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js.es6
new file mode 100644
index 00000000000..05175082704
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/status.js.es6
@@ -0,0 +1,34 @@
+/* global Vue, gl */
+/* eslint-disable no-param-reassign */
+
+((gl) => {
+ gl.VueStatusScope = Vue.extend({
+ props: [
+ 'pipeline', 'svgs', 'match',
+ ],
+ computed: {
+ cssClasses() {
+ const cssObject = { 'ci-status': true };
+ cssObject[`ci-${this.pipeline.details.status.group}`] = true;
+ return cssObject;
+ },
+ svg() {
+ return this.svgs[this.match(this.pipeline.details.status.icon)];
+ },
+ detailsPath() {
+ const { status } = this.pipeline.details;
+ return status.has_details ? status.details_path : false;
+ },
+ },
+ template: `
+ <td class="commit-link">
+ <a
+ :class='cssClasses'
+ :href='detailsPath'
+ v-html='svg + pipeline.details.status.text'
+ >
+ </a>
+ </td>
+ `,
+ });
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js.es6
new file mode 100644
index 00000000000..9e19b1564dc
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/store.js.es6
@@ -0,0 +1,65 @@
+/* global gl, Flash */
+/* eslint-disable no-param-reassign, no-underscore-dangle */
+/*= require vue_realtime_listener/index.js */
+
+((gl) => {
+ const pageValues = (headers) => {
+ const normalized = gl.utils.normalizeHeaders(headers);
+
+ const paginationInfo = {
+ perPage: +normalized['X-PER-PAGE'],
+ page: +normalized['X-PAGE'],
+ total: +normalized['X-TOTAL'],
+ totalPages: +normalized['X-TOTAL-PAGES'],
+ nextPage: +normalized['X-NEXT-PAGE'],
+ previousPage: +normalized['X-PREV-PAGE'],
+ };
+
+ return paginationInfo;
+ };
+
+ gl.PipelineStore = class {
+ fetchDataLoop(Vue, pageNum, url, apiScope) {
+ const updatePipelineNums = (count) => {
+ const { all } = count;
+ const running = count.running_or_pending;
+ document.querySelector('.js-totalbuilds-count').innerHTML = all;
+ document.querySelector('.js-running-count').innerHTML = running;
+ };
+
+ const goFetch = () =>
+ this.$http.get(`${url}?scope=${apiScope}&page=${pageNum}`)
+ .then((response) => {
+ const pageInfo = pageValues(response.headers);
+ this.pageInfo = Object.assign({}, this.pageInfo, pageInfo);
+
+ const res = JSON.parse(response.body);
+ this.count = Object.assign({}, this.count, res.count);
+ this.pipelines = Object.assign([], this.pipelines, res.pipelines);
+
+ updatePipelineNums(this.count);
+ this.pageRequest = false;
+ }, () => {
+ this.pageRequest = false;
+ return new Flash('Something went wrong on our end.');
+ });
+
+ goFetch();
+
+ const startTimeLoops = () => {
+ this.timeLoopInterval = setInterval(() => {
+ this.$children
+ .filter(e => e.$options._componentTag === 'time-ago')
+ .forEach(e => e.changeTime());
+ }, 10000);
+ };
+
+ startTimeLoops();
+
+ const removeIntervals = () => clearInterval(this.timeLoopInterval);
+ const startIntervals = () => startTimeLoops();
+
+ gl.VueRealtimeListener(removeIntervals, startIntervals);
+ }
+ };
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
new file mode 100644
index 00000000000..655110feba1
--- /dev/null
+++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
@@ -0,0 +1,73 @@
+/* global Vue, gl */
+/* eslint-disable no-param-reassign */
+
+((gl) => {
+ gl.VueTimeAgo = Vue.extend({
+ data() {
+ return {
+ currentTime: new Date(),
+ };
+ },
+ props: ['pipeline', 'svgs'],
+ computed: {
+ timeAgo() {
+ return gl.utils.getTimeago();
+ },
+ localTimeFinished() {
+ return gl.utils.formatDate(this.pipeline.details.finished_at);
+ },
+ timeStopped() {
+ const changeTime = this.currentTime;
+ const options = {
+ weekday: 'long',
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ };
+ options.timeZoneName = 'short';
+ const finished = this.pipeline.details.finished_at;
+ if (!finished && changeTime) return false;
+ return ({ words: this.timeAgo.format(finished) });
+ },
+ duration() {
+ const { duration } = this.pipeline.details;
+ const date = new Date(duration * 1000);
+
+ let hh = date.getUTCHours();
+ let mm = date.getUTCMinutes();
+ let ss = date.getSeconds();
+
+ if (hh < 10) hh = `0${hh}`;
+ if (mm < 10) mm = `0${mm}`;
+ if (ss < 10) ss = `0${ss}`;
+
+ if (duration !== null) return `${hh}:${mm}:${ss}`;
+ return false;
+ },
+ },
+ methods: {
+ changeTime() {
+ this.currentTime = new Date();
+ },
+ },
+ template: `
+ <td>
+ <p class="duration" v-if='duration'>
+ <span v-html='svgs.iconTimer'></span>
+ {{duration}}
+ </p>
+ <p class="finished-at" v-if='timeStopped'>
+ <i class="fa fa-calendar"></i>
+ <time
+ data-toggle="tooltip"
+ data-placement="top"
+ data-container="body"
+ :data-original-title='localTimeFinished'
+ >
+ {{timeStopped.words}}
+ </time>
+ </p>
+ </td>
+ `,
+ });
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/vue_realtime_listener/index.js.es6 b/app/assets/javascripts/vue_realtime_listener/index.js.es6
new file mode 100644
index 00000000000..23cac1466d2
--- /dev/null
+++ b/app/assets/javascripts/vue_realtime_listener/index.js.es6
@@ -0,0 +1,18 @@
+/* eslint-disable no-param-reassign */
+
+((gl) => {
+ gl.VueRealtimeListener = (removeIntervals, startIntervals) => {
+ const removeAll = () => {
+ removeIntervals();
+ window.removeEventListener('beforeunload', removeIntervals);
+ window.removeEventListener('focus', startIntervals);
+ window.removeEventListener('blur', removeIntervals);
+ document.removeEventListener('page:fetch', removeAll);
+ };
+
+ window.addEventListener('beforeunload', removeIntervals);
+ window.addEventListener('focus', startIntervals);
+ window.addEventListener('blur', removeIntervals);
+ document.addEventListener('page:fetch', removeAll);
+ };
+})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js
index e09b59dd5aa..a8b7be7ad06 100644
--- a/app/assets/javascripts/zen_mode.js
+++ b/app/assets/javascripts/zen_mode.js
@@ -1,4 +1,4 @@
-/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, padded-blocks, max-len */
+/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len */
/* global Dropzone */
/* global Mousetrap */
@@ -93,7 +93,5 @@
};
return ZenMode;
-
})();
-
}).call(this);
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index f1d36efb3de..8d38fc78a19 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -50,3 +50,77 @@
.pulse {
@include webkit-prefix(animation-name, pulse);
}
+
+/*
+* General hover animations
+*/
+
+
+// Sass multiple transitions mixin | https://gist.github.com/tobiasahlin/7a421fb9306a4f518aab
+// Usage: @include transition(width, height 0.3s ease-in-out);
+// Output: -webkit-transition(width 0.2s, height 0.3s ease-in-out);
+// transition(width 0.2s, height 0.3s ease-in-out);
+//
+// Pass in any number of transitions
+@mixin transition($transitions...) {
+ $unfoldedTransitions: ();
+ @each $transition in $transitions {
+ $unfoldedTransitions: append($unfoldedTransitions, unfoldTransition($transition), comma);
+ }
+
+ transition: $unfoldedTransitions;
+}
+
+@function unfoldTransition ($transition) {
+ // Default values
+ $property: all;
+ $duration: $general-hover-transition-duration;
+ $easing: $general-hover-transition-curve; // Browser default is ease, which is what we want
+ $delay: null; // Browser default is 0, which is what we want
+ $defaultProperties: ($property, $duration, $easing, $delay);
+
+ // Grab transition properties if they exist
+ $unfoldedTransition: ();
+ @for $i from 1 through length($defaultProperties) {
+ $p: null;
+ @if $i <= length($transition) {
+ $p: nth($transition, $i);
+ } @else {
+ $p: nth($defaultProperties, $i);
+ }
+ $unfoldedTransition: append($unfoldedTransition, $p);
+ }
+
+ @return $unfoldedTransition;
+}
+
+.btn,
+.side-nav-toggle {
+ @include transition(background-color, border-color, color, box-shadow);
+}
+
+.dropdown-menu-toggle,
+.avatar-circle,
+.header-user-avatar {
+ @include transition(border-color);
+}
+
+.note-action-button .link-highlight,
+.toolbar-btn,
+.dropdown-toggle-caret,
+.fa:not(.fa-bell) {
+ @include transition(color);
+}
+
+a {
+ @include transition(background-color, color, border);
+}
+
+.tree-table td,
+.well-list > li {
+ @include transition(background-color, border-color);
+}
+
+.stage-nav-item {
+ @include transition(background-color, box-shadow);
+}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index 48827578d94..1d59700543c 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -37,6 +37,8 @@
display: inline-block;
margin-left: 4px;
margin-bottom: 2px;
+ flex-shrink: 0;
+ -webkit-flex-shrink: 0;
&.s16 { margin-right: 4px; }
&.s24 { margin-right: 4px; }
@@ -52,6 +54,10 @@
border-radius: 0;
border: none;
}
+
+ &:not([href]):hover {
+ border-color: rgba($avatar-border, .2);
+ }
}
.identicon {
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 9fc9bcebc44..49907417e26 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -97,8 +97,20 @@
padding: 5px 6px;
outline: 0;
- &:hover,
+ &.disabled {
+ cursor: default;
+
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: $white-light;
+ border-color: $border-color;
+ box-shadow: none;
+ }
+ }
+
&.active,
+ &:hover,
&:active {
background-color: $row-hover;
border-color: $row-hover-border;
diff --git a/app/assets/stylesheets/framework/badges.scss b/app/assets/stylesheets/framework/badges.scss
index e9d7cda0647..47a8f44c709 100644
--- a/app/assets/stylesheets/framework/badges.scss
+++ b/app/assets/stylesheets/framework/badges.scss
@@ -4,8 +4,3 @@
color: $badge-color;
vertical-align: baseline;
}
-
-.badge-dark {
- background-color: $badge-bg-dark;
- color: $badge-color-dark;
-}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index e9aadffc73c..592ef0d647f 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -1,13 +1,13 @@
.centered-light-block {
text-align: center;
- color: $gl-gray;
+ color: $gl-text-color;
margin: 20px;
}
.nothing-here-block {
text-align: center;
padding: 20px;
- color: $gl-gray;
+ color: $gl-text-color;
font-weight: normal;
font-size: 14px;
line-height: 36px;
@@ -29,7 +29,7 @@
margin-bottom: 0;
border-top: 1px solid $white-dark;
border-bottom: 1px solid $white-dark;
- color: $gl-gray;
+ color: $gl-text-color;
&.oneline-block {
line-height: 42px;
@@ -82,7 +82,12 @@
}
.block-controls {
- float: right;
+ display: -webkit-flex;
+ display: flex;
+ -webkit-justify-content: flex-end;
+ justify-content: flex-end;
+ -webkit-flex: 1;
+ flex: 1;
.control {
float: left;
@@ -135,11 +140,11 @@
}
.cover-title {
- color: $gl-header-color;
+ color: $gl-text-color;
font-size: 23px;
h1 {
- color: $gl-gray-dark;
+ color: $gl-text-color;
margin-bottom: 6px;
font-size: 23px;
}
@@ -153,7 +158,7 @@
p {
padding: 0 $gl-padding;
- color: $gl-text-color-dark;
+ color: $gl-text-color;
}
}
@@ -211,7 +216,7 @@
display: inline;
font-weight: normal;
font-size: 24px;
- color: $gl-title-color;
+ color: $gl-text-color;
}
}
}
@@ -282,3 +287,8 @@
}
}
}
+
+.flex-container-block {
+ display: -webkit-flex;
+ display: flex;
+}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index a11f1cd7735..bb6129158d9 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -88,7 +88,7 @@
}
@mixin btn-gray {
- @include btn-color($gray-light, $border-gray-normal, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-gray-dark);
+ @include btn-color($gray-light, $border-gray-normal, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-text-color);
}
@mixin btn-white {
@@ -242,7 +242,7 @@
}
.btn-transparent {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
background-color: transparent;
border: 0;
@@ -324,7 +324,7 @@
&:focus {
cursor: text;
box-shadow: none;
- border-color: $border-color;
+ border-color: lighten($dropdown-input-focus-border, 20%);
color: $gray-darkest;
background-color: $gray-light;
}
@@ -338,7 +338,7 @@
margin-left: 10px;
i {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
}
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index ef921a8c6a9..1d2d1bfc0d7 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -1,6 +1,7 @@
.calender-block {
padding-left: 0;
padding-right: 0;
+ direction: rtl;
@media (min-width: $screen-sm-min) and (max-width: $screen-md-max) {
overflow-x: scroll;
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 67b5aa37ae7..0ce94a26a7f 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -412,7 +412,7 @@ table {
padding: 0 10px;
clip: auto;
text-decoration: none;
- color: $gl-title-color;
+ color: $gl-text-color;
background: $gray-light;
z-index: 1;
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 889366d6ddf..6bfb9a6d1cb 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -125,7 +125,8 @@
top: 100%;
left: 0;
z-index: 9;
- width: 240px;
+ max-width: 280px;
+ min-width: 240px;
margin-top: 2px;
margin-bottom: 0;
font-size: 14px;
@@ -201,7 +202,7 @@
}
.icon-play {
- fill: $gl-gray-light;
+ fill: $gl-text-color-secondary;
margin-right: 6px;
height: 12px;
width: 11px;
@@ -209,7 +210,7 @@
}
.dropdown-header {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
font-size: 13px;
line-height: 22px;
padding: 0 10px;
@@ -222,7 +223,7 @@
.unclickable {
cursor: not-allowed;
padding: 5px 8px;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
}
@@ -592,7 +593,7 @@
}
.ui-datepicker-title {
- color: $gl-gray;
+ color: $gl-text-color;
font-size: 14px;
line-height: 1;
font-weight: normal;
@@ -614,17 +615,17 @@
.dropdown-menu-inner-title {
display: block;
- color: $gl-title-color;
+ color: $gl-text-color;
font-weight: 600;
}
.dropdown-menu-inner-content {
display: block;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
.dropdown-toggle-text {
&.is-default {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
}
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 88ed0a4a17e..c51912b4ac4 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -182,3 +182,52 @@ span.idiff {
border-bottom-right-radius: 2px;
}
}
+
+.file-stats {
+ ul {
+ list-style: none;
+ margin: 0;
+ padding: 10px 0;
+
+ li {
+ padding: 3px 0;
+ line-height: 20px;
+ }
+ }
+
+ .new-file {
+ a {
+ color: $gl-text-green;
+ }
+ }
+
+ .renamed-file {
+ a {
+ color: $gl-text-orange;
+ }
+ }
+
+ .deleted-file {
+ a {
+ color: $gl-text-red;
+ }
+ }
+
+ .edit-file {
+ a {
+ color: $gl-text-color;
+ }
+ }
+
+ a {
+ text-decoration: none;
+
+ .new-file {
+ color: $notify-new-file;
+ }
+
+ .deleted-file {
+ color: $notify-deleted-file;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index 19827943385..e3da467a27c 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -23,3 +23,129 @@
}
}
+.filtered-search-container {
+ display: -webkit-flex;
+ display: flex;
+}
+
+.filtered-search-input-container {
+ display: -webkit-flex;
+ display: flex;
+ position: relative;
+ width: 100%;
+
+ .form-control {
+ padding-left: 25px;
+ padding-right: 25px;
+
+ &:focus ~ .fa-filter {
+ color: $common-gray-dark;
+ }
+ }
+
+ .fa-filter {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ color: $gray-darkest;
+ }
+
+ .fa-times {
+ right: 10px;
+ color: $gray-darkest;
+ }
+
+ .clear-search {
+ width: 35px;
+ background-color: transparent;
+ border: none;
+ position: absolute;
+ right: 0;
+ height: 100%;
+ outline: none;
+
+ &:hover .fa-times {
+ color: $common-gray-dark;
+ }
+ }
+}
+
+.dropdown-menu .filter-dropdown-item {
+ padding: 0;
+}
+
+.filter-dropdown {
+ max-height: 215px;
+ overflow: auto;
+}
+
+%filter-dropdown-item-btn-hover {
+ background-color: $dropdown-hover-color;
+ color: $white-light;
+ text-decoration: none;
+
+ .avatar {
+ border-color: $white-light;
+ }
+}
+
+.filter-dropdown-item {
+ .btn {
+ border: none;
+ width: 100%;
+ text-align: left;
+ padding: 8px 16px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ border-radius: 0;
+
+ .fa {
+ width: 15px;
+ }
+
+ .dropdown-label-box {
+ border-color: $white-light;
+ border-style: solid;
+ border-width: 1px;
+ width: 17px;
+ height: 17px;
+ }
+
+ &:hover,
+ &:focus {
+ @extend %filter-dropdown-item-btn-hover;
+ }
+ }
+
+ .dropdown-light-content {
+ font-size: 14px;
+ font-weight: 400;
+ }
+
+ .dropdown-user {
+ display: -webkit-flex;
+ display: flex;
+ }
+
+ .dropdown-user-details {
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: column;
+ flex-direction: column;
+
+ &> span {
+ white-space: normal;
+ word-break: break-all;
+ }
+ }
+}
+
+.filter-dropdown-item.dropdown-active {
+ .btn {
+ @extend %filter-dropdown-item-btn-hover;
+ }
+}
+
+.filter-dropdown-loading {
+ padding: 8px 16px;
+}
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 8726a69867b..25d6fbe465a 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -153,7 +153,7 @@ label {
}
.form-control::-webkit-input-placeholder {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
.input-group {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 971940773f7..2a01bc4d44d 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -45,7 +45,7 @@ header {
padding: 0;
.nav > li > a {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
font-size: 18px;
padding: 0;
margin: ($header-height - 28) / 2 0;
@@ -57,13 +57,21 @@ header {
&.header-user-dropdown-toggle {
margin-left: 14px;
+
+ &:hover,
+ &:focus,
+ &:active {
+ .header-user-avatar {
+ border-color: rgba($avatar-border, .2);
+ }
+ }
}
&:hover,
&:focus,
&:active {
background-color: $gray-light;
- color: darken($gl-gray-light, 30%);
+ color: $gl-text-color;
.todos-pending-count {
background: darken($todo-alert-blue, 10%);
@@ -88,7 +96,7 @@ header {
}
&.active {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
}
}
@@ -104,6 +112,7 @@ header {
&:hover {
background-color: $white-normal;
+ color: $gl-header-nav-hover-color;
}
}
}
@@ -180,6 +189,7 @@ header {
&:hover {
text-decoration: underline;
+ color: $gl-header-nav-hover-color;
}
}
@@ -198,7 +208,7 @@ header {
cursor: pointer;
&:hover {
- color: darken($color: $gl-text-color, $amount: 30%);
+ color: $gl-header-nav-hover-color;
}
}
@@ -271,4 +281,5 @@ header {
float: left;
margin-right: 5px;
border-radius: 50%;
+ border: 1px solid $avatar-border;
}
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 8624a25c052..868f28cd356 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -15,6 +15,7 @@
}
.ci-status-icon-pending,
+.ci-status-icon-failed_with_warnings,
.ci-status-icon-success_with_warnings {
color: $gl-warning;
@@ -34,10 +35,10 @@
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found {
- color: $gl-gray;
+ color: $gl-text-color;
svg {
- fill: $gl-gray;
+ fill: $gl-text-color;
}
}
diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss
index 298913108ee..46632f15f35 100644
--- a/app/assets/stylesheets/framework/issue_box.scss
+++ b/app/assets/stylesheets/framework/issue_box.scss
@@ -41,6 +41,6 @@
}
&.status-box-upcoming {
- background: $gl-gray-light;
+ background: $gl-text-color-secondary;
}
}
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 5365b62e456..29d55c44699 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -41,6 +41,21 @@ body {
}
}
+ .alert-link-group {
+ float: right;
+ }
+
+ /* Center alert text and alert action links on smaller screens */
+ @media (max-width: $screen-sm-max) {
+ .alert {
+ text-align: center;
+ }
+
+ .alert-link-group {
+ float: none;
+ }
+ }
+
/* Stripe the background colors so that adjacent alert-warnings are distinct from one another */
.alert-warning {
transition: background-color 0.15s, border-color 0.15s;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 277d4202950..426596027de 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -128,7 +128,7 @@ ul.content-list {
}
a {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
.member-group-link {
@@ -155,7 +155,8 @@ ul.content-list {
}
> .btn,
- > .btn-group {
+ > .btn-group,
+ > .dropdown.inline {
margin-right: $gl-padding-top;
display: inline-block;
margin-top: 3px;
@@ -163,6 +164,10 @@ ul.content-list {
&:last-child {
margin-right: 0;
+
+ @media(max-width: $screen-xs-max) {
+ margin: 0 auto;
+ }
}
}
@@ -230,20 +235,52 @@ ul.content-list {
}
.label-default {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
}
-.panel > .content-list > li {
- padding: $gl-padding-top $gl-padding;
+// Table list
+.table-list {
+ display: table;
+ width: 100%;
+
+ .table-list-row {
+ display: table-row;
+ }
+
+ .table-list-cell {
+ display: table-cell;
+ vertical-align: top;
+ padding: 10px 16px;
+ border-bottom: 1px solid $gray-darker;
+
+ &.avatar-cell {
+ width: 36px;
+ padding-right: 0;
+
+ img {
+ margin-right: 0;
+ }
+ }
+ }
+
+ &.table-wide {
+ .table-list-cell {
+ &:last-of-type {
+ padding-right: 0;
+ }
- &.commit {
- @media (min-width: $screen-sm-min) {
- padding-left: 46px + $gl-padding;
+ &:first-of-type {
+ padding-left: 0;
+ }
}
}
}
+.panel > .content-list > li {
+ padding: $gl-padding-top $gl-padding;
+}
+
ul.controls {
float: right;
list-style: none;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index e30d81d09f0..5bff694658c 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -73,7 +73,7 @@
}
.referenced-users {
- color: $gl-header-color;
+ color: $gl-text-color;
padding-top: 10px;
}
@@ -135,7 +135,7 @@
.toolbar-btn {
float: left;
padding: 0 5px;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
background: transparent;
border: 0;
outline: 0;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 4f2ac77f228..1acd06122a3 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -46,7 +46,7 @@
&.light {
a {
- color: $gl-gray;
+ color: $gl-text-color;
}
}
}
diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss
index 7eb9962ba33..8e2c56a8488 100644
--- a/app/assets/stylesheets/framework/mobile.scss
+++ b/app/assets/stylesheets/framework/mobile.scss
@@ -23,21 +23,21 @@
margin-right: 0;
}
- .issues-details-filters,
+ .issues-details-filters:not(.filtered-search-block),
.dash-projects-filters,
.check-all-holder {
display: none;
}
- .rss-btn {
+ .issues-holder .issue-check {
display: none;
}
- .project-home-links {
+ .rss-btn {
display: none;
}
- .project-avatar {
+ .project-home-links {
display: none;
}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 5abda13a963..401c2d0f6ee 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -51,7 +51,7 @@
margin-bottom: -1px;
font-size: 14px;
line-height: 28px;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
border-bottom: 2px solid transparent;
&:hover,
@@ -101,7 +101,7 @@
&:hover,
&:active,
&:focus {
- border-bottom: none;
+ border-color: transparent;
}
}
}
@@ -315,7 +315,7 @@
.fa-caret-down {
margin-left: 5px;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
.dropdown {
diff --git a/app/assets/stylesheets/framework/page-header.scss b/app/assets/stylesheets/framework/page-header.scss
index 625bea96aaa..5f4211147f3 100644
--- a/app/assets/stylesheets/framework/page-header.scss
+++ b/app/assets/stylesheets/framework/page-header.scss
@@ -14,7 +14,7 @@
.header-action-buttons {
i {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
font-size: 13px;
margin-right: 3px;
}
@@ -42,14 +42,10 @@
.commit-committer-link,
.commit-author-link {
- color: $gl-gray;
+ color: $gl-text-color;
font-weight: bold;
}
- .fa-clipboard {
- color: $dropdown-title-btn-color;
- }
-
.commit-info {
&.branches {
margin-left: 8px;
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 9d8d08dff88..efe93724013 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -48,3 +48,11 @@
line-height: inherit;
}
}
+
+.panel-default {
+ .table-list-row:last-child {
+ .table-list-cell {
+ border-bottom: 0;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index a8641e83154..f0b03710c79 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -183,7 +183,9 @@
&.right-sidebar-expanded {
.line-resolve-all-container {
- display: none;
+ @media (min-width: $sidebar-breakpoint) {
+ display: none;
+ }
}
}
}
@@ -234,9 +236,13 @@ header.header-sidebar-pinned {
@media (min-width: $screen-md-min) {
padding-right: $gutter_width;
- .merge-request-tabs-holder.affix {
+ &:not(.with-overlay) .merge-request-tabs-holder.affix {
right: $gutter_width;
}
+
+ &.with-overlay .merge-request-tabs-holder.affix {
+ right: $sidebar_collapsed_width;
+ }
}
&.with-overlay {
diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss
index 6078505807e..ff185cd8767 100644
--- a/app/assets/stylesheets/framework/timeline.scss
+++ b/app/assets/stylesheets/framework/timeline.scss
@@ -7,7 +7,7 @@
.timeline-entry {
padding: $gl-padding $gl-btn-padding 11px;
border-color: $white-normal;
- color: $gl-gray;
+ color: $gl-text-color;
border-bottom: 1px solid $border-white-light;
&:target {
@@ -32,7 +32,7 @@
.system-note {
.note-text {
- color: $gl-gray !important;
+ color: $gl-text-color !important;
}
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss
index 28cbae9a449..12d56359d7d 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap.scss
@@ -98,7 +98,7 @@
&.label-gray {
background-color: $label-gray-bg;
- color: $gl-gray;
+ color: $gl-text-color;
text-shadow: none;
}
diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
index 876adf7f712..0fc89d5976a 100644
--- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
+++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss
@@ -65,11 +65,11 @@ $legend-color: $text-color;
//
//##
-$pagination-color: $gl-gray;
+$pagination-color: $gl-text-color;
$pagination-bg: $white-light;
$pagination-border: $border-color;
-$pagination-hover-color: $gl-gray;
+$pagination-hover-color: $gl-text-color;
$pagination-hover-bg: $row-hover;
$pagination-hover-border: $border-color;
@@ -121,6 +121,9 @@ $panel-default-heading-bg: $gray-light;
$panel-footer-bg: $gray-light;
$panel-inner-border: $border-color;
+$badge-bg: $badge-bg;
+$badge-color: $badge-color;
+
//== Wells
//
//##
@@ -154,7 +157,7 @@ $nav-link-padding: 13px $gl-padding;
//
//##
$pre-bg: $gray-light !default;
-$pre-color: $gl-gray !default;
+$pre-color: $gl-text-color !default;
$pre-border-color: $border-color;
$table-bg-accent: $gray-light;
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index d906d26bba9..54958973f15 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -1,5 +1,5 @@
@mixin md-typography {
- color: $md-text-color;
+ color: $gl-text-color;
word-wrap: break-word;
a {
@@ -10,7 +10,7 @@
max-width: 100%;
}
- *:first-child {
+ *:first-child:not(.katex-display) {
margin-top: 0;
}
@@ -50,14 +50,14 @@
margin: 16px 0 10px;
padding: 0 0 0.3em;
border-bottom: 1px solid $white-dark;
- color: $gl-gray-dark;
+ color: $gl-text-color;
}
h2 {
font-size: 1.5em;
font-weight: 600;
margin: 16px 0 10px;
- color: $gl-gray-dark;
+ color: $gl-text-color;
}
h3 {
@@ -100,7 +100,7 @@
}
p {
- color: $gl-text-color-dark;
+ color: $gl-text-color;
margin: 6px 0 0;
}
@@ -108,7 +108,7 @@
@extend .table;
@extend .table-bordered;
margin: 12px 0;
- color: $gl-text-color-dark;
+ color: $gl-text-color;
th {
background: $label-gray-bg;
@@ -230,7 +230,7 @@ h3,
h4,
h5,
h6 {
- color: $gl-title-color;
+ color: $gl-text-color;
font-weight: 600;
}
@@ -292,7 +292,7 @@ h2,
h3,
h4 {
small {
- color: $gl-gray;
+ color: $gl-text-color;
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b0c4a6edf57..7809d4866f1 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -5,7 +5,7 @@ $sidebar_collapsed_width: 62px;
$sidebar_width: 220px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
-$gutter_inner_width: 258px;
+$gutter_inner_width: 250px;
$sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1024px;
@@ -56,6 +56,7 @@ $black-transparent: rgba(0, 0, 0, 0.3);
$border-white-light: darken($white-light, $darken-border-factor);
$border-white-normal: darken($white-normal, $darken-border-factor);
+$border-gray-light: darken($gray-light, $darken-border-factor);
$border-gray-normal: darken($gray-normal, $darken-border-factor);
$border-gray-dark: darken($white-normal, $darken-border-factor);
@@ -85,6 +86,7 @@ $warning-message-border: #f0e2bb;
*/
$border-color: #e5e5e5;
$focus-border-color: #3aabf0;
+$sidebar-collapsed-icon-color: #999;
$well-expand-item: #e8f2f7;
$well-inner-border: #eef0f2;
$well-light-border: #f1f1f1;
@@ -94,29 +96,26 @@ $well-light-text-color: #5b6169;
* Text
*/
$gl-font-size: 14px;
-$gl-title-color: #333;
-$gl-text-color: #5c5c5c;
-$gl-text-color-dark: #5c5d5e;
-$gl-text-color-light: #8c8c8c;
+$gl-text-color: rgba(0, 0, 0, .85);
+$gl-text-color-secondary: rgba(0, 0, 0, .55);
+$gl-text-color-disabled: rgba(0, 0, 0, .35);
$gl-text-green: #4a2;
$gl-text-red: #d12f19;
$gl-text-orange: #d90;
$gl-link-color: #3777b0;
-$gl-diff-text-color: #555;
-$gl-dark-link-color: #333;
-$gl-gray-light: #8f8f8f;
$gl-grayish-blue: #7f8fa4;
$gl-gray: $gl-text-color;
$gl-gray-dark: #313236;
$gl-header-color: #4c4e54;
+$gl-header-nav-hover-color: #434343;
/*
* Lists
*/
$list-font-size: $gl-font-size;
-$list-title-color: $gl-title-color;
+$list-title-color: $gl-text-color;
$list-text-color: $gl-text-color;
-$list-text-disabled-color: #888;
+$list-text-disabled-color: $gl-text-color-disabled;
$list-border-light: #eee;
$list-border: rgba(0, 0, 0, 0.05);
$list-text-height: 42px;
@@ -127,7 +126,6 @@ $list-warning-row-color: #8a6d3b;
/*
* Markdown
*/
-$md-text-color: $gl-text-color;
$md-link-color: $gl-link-color;
$md-area-border: #ddd;
@@ -168,9 +166,7 @@ $btn-side-margin: 10px;
$btn-sm-side-margin: 7px;
$btn-xs-side-margin: 5px;
$issue-status-expired: #cea61b;
-$issuable-sidebar-color: #999;
-$issuable-avatar-hover-border: #999;
-$issuable-clipboard-color: #999;
+$issuable-sidebar-color: $gl-text-color-secondary;
$show-aside-bg: #eee;
$show-aside-color: #777;
$show-aside-shadow: #ddd;
@@ -182,6 +178,9 @@ $count-arrow-border: #dce0e5;
$save-project-loader-color: #555;
$divergence-graph-bar-bg: #ccc;
$divergence-graph-separator-bg: #ccc;
+$general-hover-transition-duration: 100ms;
+$general-hover-transition-curve: linear;
+
/*
* Common component specific colors
@@ -274,18 +273,22 @@ $dropdown-toggle-active-border-color: darken($border-color, 14%);
/*
+* Filtered Search
+*/
+$dropdown-hover-color: #3b86ff;
+
+/*
* Buttons
*/
$btn-active-gray: #ececec;
$btn-active-gray-light: e4e7ed;
+$btn-white-active: #848484;
/*
* Badges
*/
-$badge-bg: #f3f3f3;
-$badge-bg-dark: #eee;
-$badge-color: #929292;
-$badge-color-dark: #8f8f8f;
+$badge-bg: #eee;
+$badge-color: $gl-text-color-secondary;
/*
* Award emoji
@@ -304,8 +307,8 @@ $location-icon-color: #e7e9ed;
/*
* Notes
*/
-$notes-light-color: #8e8e8e;
-$notes-role-color: #8e8e8e;
+$notes-light-color: $gl-text-color-secondary;
+$notes-role-color: $gl-text-color-secondary;
$note-disabled-comment-color: #b2b2b2;
$note-targe3-outside: #fffff0;
$note-targe3-inside: #ffffd3;
@@ -330,7 +333,7 @@ $calendar-user-contrib-text: #959494;
$cycle-analytics-box-padding: 30px;
$cycle-analytics-box-text-color: #8c8c8c;
$cycle-analytics-big-font: 19px;
-$cycle-analytics-dark-text: $gl-title-color;
+$cycle-analytics-dark-text: $gl-text-color;
$cycle-analytics-light-gray: #bfbfbf;
$cycle-analytics-dismiss-icon-color: #b2b2b2;
@@ -376,14 +379,13 @@ $callout-success-color: #3c763d;
/*
* Commit Page
*/
-$commit-committer-color: #999;
$commit-max-width-marker-color: rgba(0, 0, 0, 0.0);
$commit-message-text-area-bg: rgba(0, 0, 0, 0.0);
/*
* Common
*/
-$common-gray: $gl-gray;
+$common-gray: $gl-text-color;
$common-gray-light: #bbb;
$common-gray-dark: #444;
$common-red: $gl-text-red;
@@ -434,6 +436,7 @@ $help-shortcut-header-color: #333;
*/
$issues-today-bg: #f3fff2;
$issues-today-border: #e1e8d5;
+$compare-display-color: #888;
/*
* jQuery UI
@@ -538,3 +541,4 @@ Pipeline Graph
*/
$stage-hover-bg: #eaf3fc;
$stage-hover-border: #d1e7fc;
+$action-icon-color: #d6d6d6; \ No newline at end of file
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index f9c850ebdc8..32eb750180f 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -1,6 +1,6 @@
.info-well {
background: $gray-light;
- color: $gl-gray;
+ color: $gl-text-color;
border: 1px solid $border-color;
border-radius: $border-radius-default;
diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss
index 84b639fabf5..97ade638db6 100644
--- a/app/assets/stylesheets/framework/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
@@ -40,7 +40,7 @@
}
.zen-control-full {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
&:hover {
color: $gl-link-color;
diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss
index cb923166b25..6f2e746d4b0 100644
--- a/app/assets/stylesheets/highlight/dark.scss
+++ b/app/assets/stylesheets/highlight/dark.scss
@@ -13,6 +13,8 @@ $dark-main-bg: #1d1f21;
$dark-main-color: #1d1f21;
$dark-line-color: #c5c8c6;
$dark-line-num-color: rgba(255, 255, 255, 0.3);
+$dark-line-num-color-new: #627165;
+$dark-line-num-color-old: #806565;
$dark-diff-not-empty-bg: #557;
$dark-highlight-bg: #ffe792;
$dark-highlight-color: $black;
@@ -89,7 +91,6 @@ $dark-il: #de935f;
.diff-line-num,
.diff-line-num a {
- color: $dark-main-color;
color: $dark-line-num-color;
}
@@ -121,11 +122,21 @@ $dark-il: #de935f;
.diff-line-num.new,
.line_content.new {
@include diff_background($dark-new-bg, $dark-new-idiff, $dark-border);
+
+ &::before,
+ a {
+ color: $dark-line-num-color-new;
+ }
}
.diff-line-num.old,
.line_content.old {
@include diff_background($dark-old-bg, $dark-old-idiff, $dark-border);
+
+ &::before,
+ a {
+ color: $dark-line-num-color-old;
+ }
}
.line_content.match {
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index d8510baad8a..2144a5f7466 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -7,6 +7,8 @@ $monokai-bg: #272822;
$monokai-border: #555;
$monokai-text-color: #f8f8f2;
$monokai-line-num-color: rgba(255, 255, 255, 0.3);
+$monokai-line-num-color-new: #707565;
+$monokai-line-num-color-old: #7e736f;
$monokai-line-empty-bg: #49483e;
$monokai-line-empty-border: darken($monokai-line-empty-bg, 15%);
$monokai-diff-border: #808080;
@@ -120,11 +122,21 @@ $monokai-gi: #a6e22e;
.diff-line-num.new,
.line_content.new {
@include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border);
+
+ &::before,
+ a {
+ color: $monokai-line-num-color-new;
+ }
}
.diff-line-num.old,
.line_content.old {
@include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border);
+
+ &::before,
+ a {
+ color: $monokai-line-num-color-old;
+ }
}
.line_content.match {
diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss
index 874aecb5e16..2cb1d18f12f 100644
--- a/app/assets/stylesheets/highlight/solarized_dark.scss
+++ b/app/assets/stylesheets/highlight/solarized_dark.scss
@@ -13,6 +13,8 @@ $solarized-dark-pre-color: #93a1a1;
$solarized-dark-pre-border: #113b46;
$solarized-dark-line-bg: #002b36;
$solarized-dark-line-color: rgba(255, 255, 255, 0.3);
+$solarized-dark-line-color-new: #5a766c;
+$solarized-dark-line-color-old: #7a6c71;
$solarized-dark-highlight: #094554;
$solarized-dark-hll-bg: #174652;
$solarized-dark-c: #586e75;
@@ -124,11 +126,21 @@ $solarized-dark-il: #2aa198;
.diff-line-num.new,
.line_content.new {
@include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border);
+
+ &::before,
+ a {
+ color: $solarized-dark-line-color-new;
+ }
}
.diff-line-num.old,
.line_content.old {
@include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border);
+
+ &::before,
+ a {
+ color: $solarized-dark-line-color-old;
+ }
}
.line_content.match {
diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss
index 499a1c108b8..b72c4326730 100644
--- a/app/assets/stylesheets/highlight/solarized_light.scss
+++ b/app/assets/stylesheets/highlight/solarized_light.scss
@@ -13,6 +13,9 @@ $solarized-light-pre-bg: #002b36;
$solarized-light-pre-bg: #fdf6e3;
$solarized-light-pre-color: #586e75;
$solarized-light-line-bg: #fdf6e3;
+$solarized-light-line-color: rgba(0, 0, 0, 0.3);
+$solarized-light-line-color-new: #a1a080;
+$solarized-light-line-color-old: #ad9186;
$solarized-light-highlight: #eee8d5;
$solarized-light-hll-bg: #ddd8c5;
$solarized-light-c: #93a1a1;
@@ -98,7 +101,7 @@ $solarized-light-il: #2aa198;
.diff-line-num,
.diff-line-num a {
- color: $black-transparent;
+ color: $solarized-light-line-color;
}
// Code itself
@@ -130,11 +133,21 @@ $solarized-light-il: #2aa198;
.line_content.new {
@include diff_background($solarized-light-new-bg,
$solarized-light-new-idiff, $solarized-light-border);
+
+ &::before,
+ a {
+ color: $solarized-light-line-color-new;
+ }
}
.diff-line-num.old,
.line_content.old {
@include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border);
+
+ &::before,
+ a {
+ color: $solarized-light-line-color-old;
+ }
}
.line_content.match {
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 54a5664a874..398fbfd3b18 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -3,7 +3,7 @@
/*
* White Syntax Colors
*/
-$white-code-color: #333;
+$white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
@@ -108,11 +108,19 @@ $white-gc-bg: #eaf2f5;
&.old {
background-color: $line-number-old;
border-color: $line-removed-dark;
+
+ a {
+ color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%);
+ }
}
&.new {
background-color: $line-number-new;
border-color: $line-added-dark;
+
+ a {
+ color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%);
+ }
}
&.hll:not(.empty-cell) {
@@ -125,6 +133,10 @@ $white-gc-bg: #eaf2f5;
&.old {
background-color: $line-removed;
+ &::before {
+ color: scale-color($line-number-old,$red: -30%, $green: -30%, $blue: -30%);
+ }
+
span.idiff {
background-color: $line-removed-dark;
}
@@ -133,6 +145,10 @@ $white-gc-bg: #eaf2f5;
&.new {
background-color: $line-added;
+ &::before {
+ color: scale-color($line-number-new,$red: -30%, $green: -30%, $blue: -30%);
+ }
+
span.idiff {
background-color: $line-added-dark;
}
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
index ddc382362f7..a81e5eb5ebf 100644
--- a/app/assets/stylesheets/notify.scss
+++ b/app/assets/stylesheets/notify.scss
@@ -18,15 +18,3 @@ p.details {
pre.commit-message {
white-space: pre-wrap;
}
-
-.file-stats > a {
- text-decoration: none;
-
- > .new-file {
- color: $notify-new-file;
- }
-
- > .deleted-file {
- color: $notify-deleted-file;
- }
-}
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 76a88d96183..f2d60bff2b5 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -74,6 +74,7 @@
height: 475px; // Needed for PhantomJS
height: calc(100vh - 220px);
min-height: 475px;
+ transition: width .2s;
&.is-compact {
width: calc(100% - 290px);
@@ -259,7 +260,7 @@
.board-list-count {
padding: 10px 0;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
font-size: 13px;
> .fa {
@@ -338,3 +339,18 @@
}
}
}
+
+.right-sidebar.right-sidebar-expanded {
+ &.boards-sidebar-slide-enter-active,
+ &.boards-sidebar-slide-leave-active {
+ transition: width .2s,
+ padding .2s;
+ }
+
+ &.boards-sidebar-slide-enter,
+ &.boards-sidebar-slide-leave-active {
+ width: 0;
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss
new file mode 100644
index 00000000000..3e2fa8ca88d
--- /dev/null
+++ b/app/assets/stylesheets/pages/branches.scss
@@ -0,0 +1,55 @@
+.divergence-graph {
+ padding: 12px 12px 0 0;
+ float: right;
+
+ .graph-side {
+ position: relative;
+ width: 80px;
+ height: 22px;
+ padding: 5px 0 13px;
+ float: left;
+
+ .bar {
+ position: absolute;
+ height: 4px;
+ background-color: $divergence-graph-bar-bg;
+ }
+
+ .bar-behind {
+ right: 0;
+ border-radius: 3px 0 0 3px;
+ }
+
+ .bar-ahead {
+ left: 0;
+ border-radius: 0 3px 3px 0;
+ }
+
+ .count {
+ padding-top: 6px;
+ padding-bottom: 0;
+ font-size: 12px;
+ color: $gl-text-color;
+ display: block;
+ }
+
+ .count-behind {
+ padding-right: 4px;
+ text-align: right;
+ }
+
+ .count-ahead {
+ padding-left: 4px;
+ text-align: left;
+ }
+ }
+
+ .graph-separator {
+ position: relative;
+ width: 1px;
+ height: 18px;
+ margin: 5px 0 0;
+ float: left;
+ background-color: $divergence-graph-separator-bg;
+ }
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index f9e8d297c05..fd101d43b5b 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -160,7 +160,7 @@
flex: 1;
a {
- color: $gl-gray;
+ color: $gl-text-color;
&:hover {
color: $gl-link-color;
@@ -357,7 +357,7 @@
}
.build-light-text {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
.build-gutter-toggle {
diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss
index d1cd1e5d848..90643832390 100644
--- a/app/assets/stylesheets/pages/ci_projects.scss
+++ b/app/assets/stylesheets/pages/ci_projects.scss
@@ -18,7 +18,7 @@
}
td {
- color: $gl-gray;
+ color: $gl-text-color;
vertical-align: middle !important;
a {
diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss
deleted file mode 100644
index bf656d0e28e..00000000000
--- a/app/assets/stylesheets/pages/commit.scss
+++ /dev/null
@@ -1,132 +0,0 @@
-.commit-title {
- display: block;
-}
-
-.commit-author,
-.commit-committer {
- display: block;
- color: $commit-committer-color;
- font-weight: normal;
- font-style: italic;
-}
-
-.commit-author strong,
-.commit-committer strong {
- font-weight: bold;
- font-style: normal;
-}
-
-.commit-description {
- background: none;
- border: none;
- margin: 0;
- padding: 0;
- margin-top: 10px;
- word-break: normal;
- white-space: pre-wrap;
-}
-
-.js-details-expand {
- &:hover {
- text-decoration: none;
- }
-}
-
-.ci-status-link {
- svg {
- overflow: visible;
- }
-}
-
-.commit-box {
- border-top: 1px solid $border-color;
- padding: $gl-padding 0;
-
- .commit-title {
- margin: 0;
- font-size: 23px;
- color: $gl-gray-dark;
- }
-
- .commit-description {
- margin-top: 15px;
- }
-}
-
-.commit-hash-full {
- @media (max-width: $screen-sm-max) {
- width: 80px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- display: inline-block;
- vertical-align: bottom;
- }
-}
-
-.file-stats {
- ul {
- list-style: none;
- margin: 0;
- padding: 10px 0;
-
- li {
- padding: 3px 0;
- line-height: 20px;
- }
- }
-
- .new-file {
- a {
- color: $gl-text-green;
- }
- }
-
- .renamed-file {
- a {
- color: $gl-text-orange;
- }
- }
-
- .deleted-file {
- a {
- color: $gl-text-red;
- }
- }
-
- .edit-file {
- a {
- color: $gl-text-color;
- }
- }
-}
-
-/*
- * Commit message textarea for web editor and
- * custom merge request message
- */
-.commit-message-container {
- background-color: $body-bg;
- position: relative;
- font-family: $monospace_font;
- $left: 12px;
- overflow: hidden; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
- .max-width-marker {
- width: 72ch;
- color: $commit-max-width-marker-color;
- font-family: inherit;
- left: $left;
- height: 100%;
- border-right: 1px solid mix($input-border, $white-light);
- position: absolute;
- z-index: 1;
- }
-
- > textarea {
- background-color: $commit-message-text-area-bg;
- font-family: inherit;
- padding-left: $left;
- position: relative;
- z-index: 2;
- }
-}
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index e76e1a73b25..fef8e8eec27 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -1,6 +1,75 @@
+.commit-description {
+ background: none;
+ border: none;
+ padding: 0;
+ margin-top: 10px;
+ word-break: normal;
+ white-space: pre-wrap;
+}
+
+.js-details-expand {
+ &:hover {
+ text-decoration: none;
+ }
+}
+
+.commit-box {
+ border-top: 1px solid $border-color;
+ padding: $gl-padding 0;
+
+ .commit-title {
+ margin: 0;
+ color: $gl-text-color;
+ }
+
+ .commit-description {
+ margin-top: 15px;
+ }
+}
+
+.commit-hash-full {
+ @media (max-width: $screen-sm-max) {
+ width: 80px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+ vertical-align: bottom;
+ }
+}
+
+/*
+ * Commit message textarea for web editor and
+ * custom merge request message
+ */
+.commit-message-container {
+ background-color: $body-bg;
+ position: relative;
+ font-family: $monospace_font;
+ $left: 12px;
+ overflow: hidden; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
+ .max-width-marker {
+ width: 72ch;
+ color: $commit-max-width-marker-color;
+ font-family: inherit;
+ left: $left;
+ height: 100%;
+ border-right: 1px solid mix($input-border, $white-light);
+ position: absolute;
+ z-index: 1;
+ }
+
+ textarea {
+ background-color: $commit-message-text-area-bg;
+ font-family: inherit;
+ padding-left: $left;
+ position: relative;
+ z-index: 2;
+ }
+}
+
+
.commits-compare-switch {
- @include btn-default;
- @include btn-white;
float: left;
margin-right: 9px;
}
@@ -8,7 +77,6 @@
.commit-header {
padding: 5px 10px;
background-color: $gray-light;
- border-top: 1px solid $gray-darker;
border-bottom: 1px solid $gray-darker;
font-size: 14px;
@@ -18,8 +86,6 @@
}
.commit-row-title {
- line-height: 1.35;
-
.notes_count {
float: right;
margin-right: 10px;
@@ -30,15 +96,14 @@
}
.commit-row-message {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
-
}
.text-expander {
display: inline-block;
background: $gray-light;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
padding: 0 5px;
cursor: pointer;
border: 1px solid $border-gray-dark;
@@ -54,9 +119,8 @@
.commit-actions {
@media (min-width: $screen-sm-min) {
- float: right;
- margin-left: $gl-padding;
- margin-top: 2px;
+ width: 300px;
+ text-align: right;
font-size: 0;
}
@@ -86,35 +150,13 @@
.commit,
.generic_commit_status {
- padding: 10px 0;
- position: relative;
-
- @media (min-width: $screen-sm-min) {
- padding-left: 46px;
- }
-
- &:not(:last-child) {
- border-bottom: 1px solid $gray-darker;
- }
a,
button {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
vertical-align: baseline;
}
- .avatar {
- margin-left: -46px;
- }
-
- .item-title {
- display: inline-block;
-
- @media (min-width: $screen-sm-min) {
- max-width: 70%;
- }
- }
-
.commit-row-description {
font-size: 14px;
border-left: 1px solid $white-normal;
@@ -134,20 +176,7 @@
}
a {
- color: $gl-dark-link-color;
- }
- }
-
- .commit-row-info {
- color: $gl-gray;
- line-height: 1.35;
-
- a {
- color: $gl-gray;
- }
-
- .avatar {
- margin-right: 8px;
+ color: $gl-text-color;
}
}
@@ -164,7 +193,7 @@
}
.branch-commit {
- color: $gl-gray;
+ color: $gl-text-color;
.commit-icon {
text-align: center;
@@ -174,7 +203,7 @@
height: 14px;
width: 14px;
vertical-align: middle;
- fill: $gl-gray-light;
+ fill: $gl-text-color-secondary;
}
}
@@ -183,62 +212,6 @@
}
.commit-row-message {
- color: $gl-gray;
- }
-}
-
-.divergence-graph {
- padding: 12px 12px 0 0;
- float: right;
-
- .graph-side {
- position: relative;
- width: 80px;
- height: 22px;
- padding: 5px 0 13px;
- float: left;
-
- .bar {
- position: absolute;
- height: 4px;
- background-color: $divergence-graph-bar-bg;
- }
-
- .bar-behind {
- right: 0;
- border-radius: 3px 0 0 3px;
- }
-
- .bar-ahead {
- left: 0;
- border-radius: 0 3px 3px 0;
- }
-
- .count {
- padding-top: 6px;
- padding-bottom: 0;
- font-size: 12px;
- color: $gl-title-color;
- display: block;
- }
-
- .count-behind {
- padding-right: 4px;
- text-align: right;
- }
-
- .count-ahead {
- padding-left: 4px;
- text-align: left;
- }
- }
-
- .graph-separator {
- position: relative;
- width: 1px;
- height: 18px;
- margin: 5px 0 0;
- float: left;
- background-color: $divergence-graph-separator-bg;
+ color: $gl-text-color;
}
}
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 9ce261eafef..cda069e6c0e 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -20,6 +20,10 @@
.fa {
color: $cycle-analytics-light-gray;
+
+ &:hover {
+ color: $gl-text-color;
+ }
}
.stage-header {
@@ -111,14 +115,14 @@
line-height: 19px;
font-size: 14px;
font-weight: 600;
- color: $gl-title-color;
+ color: $gl-text-color;
}
&.text {
color: $layout-link-gray;
&.value-col {
- color: $gl-title-color;
+ color: $gl-text-color;
}
}
}
@@ -260,7 +264,7 @@
.stage-empty,
.not-available {
- color: $gl-text-color-light;
+ color: $gl-text-color-secondary;
}
}
}
@@ -327,7 +331,7 @@
@include text-overflow();
a {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
}
}
@@ -355,7 +359,7 @@
.issue-link,
.commit-author-link,
.issue-author-link {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
// Custom CSS for components
@@ -396,11 +400,11 @@
}
.item-build-name {
- color: $gl-title-color;
+ color: $gl-text-color;
}
.pipeline-id {
- color: $gl-title-color;
+ color: $gl-text-color;
padding: 0 3px 0 0;
}
@@ -423,7 +427,7 @@
}
.fa {
- color: $gl-text-color-light;
+ color: $gl-text-color-secondary;
font-size: $code_font_size;
}
}
@@ -435,7 +439,7 @@
width: 75%;
margin: 0 auto;
padding-top: 130px;
- color: $gl-text-color-light;
+ color: $gl-text-color-secondary;
h4 {
color: $gl-text-color;
diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss
index 9b28df1afc5..46fd19c93f9 100644
--- a/app/assets/stylesheets/pages/detail_page.scss
+++ b/app/assets/stylesheets/pages/detail_page.scss
@@ -1,15 +1,15 @@
.detail-page-header {
padding: $gl-padding-top 0;
border-bottom: 1px solid $border-color;
- color: $gl-text-color-dark;
+ color: $gl-text-color;
line-height: 34px;
.author {
- color: $gl-text-color-dark;
+ color: $gl-text-color;
}
.identifier {
- color: $gl-text-color-dark;
+ color: $gl-text-color;
}
.issue_created_ago,
@@ -22,7 +22,7 @@
.title {
margin: 0 0 16px;
font-size: 2em;
- color: $gl-gray-dark;
+ color: $gl-text-color;
padding: 0 0 0.3em;
border-bottom: 1px solid $white-dark;
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index f30795fd2c2..96ba7c40634 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -14,7 +14,7 @@
background: $gray-light;
border-bottom: 1px solid $border-color;
padding: 10px 16px;
- color: $gl-diff-text-color;
+ color: $gl-text-color;
z-index: 10;
border-radius: 3px 3px 0 0;
@@ -50,7 +50,7 @@
overflow: auto;
overflow-y: hidden;
background: $white-light;
- color: $gl-title-color;
+ color: $gl-text-color;
border-radius: 0 0 3px 3px;
.unfold {
@@ -380,7 +380,7 @@
}
cursor: default;
- color: $gl-title-color;
+ color: $gl-text-color;
}
&.disabled {
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 5517dc5dcbd..778ef01430e 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -72,25 +72,25 @@
.external-url,
.dropdown-new {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
.dropdown-menu {
.fa {
margin-right: 6px;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
}
.build-link,
.branch-name {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
.stop-env-link,
.external-url {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
.stop-env-icon {
font-size: 14px;
@@ -101,7 +101,7 @@
.build-column {
.build-link {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
.avatar {
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 98925c2d0cb..b989d72ce1c 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -21,7 +21,7 @@
}
a {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
.avatar {
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 16bff5f1e03..d377526e655 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -13,7 +13,7 @@
.stats {
float: right;
line-height: $list-text-height;
- color: $gl-gray;
+ color: $gl-text-color;
span {
margin-right: 15px;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 6b4d1f85564..4ef95d27f4f 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -1,3 +1,48 @@
+// 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;
+}
+
+.limit-container-width {
+ .detail-page-header {
+ @extend .fixed-width-container;
+ }
+
+ .issuable-details {
+ .detail-page-description,
+ .mr-source-target,
+ .mr-state-widget,
+ .merge-manually {
+ @extend .fixed-width-container;
+ }
+
+ .merge-request-tabs-holder {
+ &.affix {
+ border-bottom: 1px solid $border-color;
+
+ .nav-links {
+ border: 0;
+ }
+ }
+ }
+ }
+
+ .merge-request-details {
+ .emoji-list-container {
+ @extend .fixed-width-container;
+ }
+ }
+
+ .diffs {
+ .mr-version-controls,
+ .files-changed {
+ @extend .fixed-width-container;
+ }
+ }
+}
+
.issuable-details {
section {
.issuable-discussion {
@@ -5,6 +50,12 @@
}
}
+ .title {
+ padding: 0;
+ margin: 0;
+ border-bottom: none;
+ }
+
// Border around images in issue and MR descriptions.
.description img:not(.emoji) {
border: 1px solid $white-normal;
@@ -97,10 +148,10 @@
}
.edit-link {
- color: $gl-gray;
+ color: $gl-text-color;
- &:hover {
- color: $md-link-color;
+ &:not([href]):hover {
+ color: rgba($avatar-border, .2);
}
}
}
@@ -133,7 +184,7 @@
}
.btn-clipboard:hover {
- color: $gl-gray;
+ color: $gl-text-color;
}
}
@@ -168,7 +219,7 @@
}
.no-value {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
.sidebar-collapsed-icon {
@@ -236,7 +287,7 @@
color: $issuable-sidebar-color;
&:hover {
- color: $gl-gray;
+ color: $gl-text-color;
}
span {
@@ -249,16 +300,16 @@
}
.avatar:hover {
- border-color: $issuable-avatar-hover-border;
+ border-color: $issuable-sidebar-color;
}
.btn-clipboard {
border: none;
- color: $issuable-clipboard-color;
+ color: $issuable-sidebar-color;
&:hover {
background: transparent;
- color: $gl-gray;
+ color: $gl-text-color;
}
}
}
@@ -277,6 +328,10 @@
&:hover {
color: $md-link-color;
text-decoration: none;
+
+ .avatar {
+ border-color: rgba($avatar-border, .2);
+ }
}
}
@@ -318,6 +373,10 @@
display: inline-block;
padding: 5px;
+ &:nth-of-type(7n) {
+ padding-right: 0;
+ }
+
.author_link {
display: block;
}
@@ -332,7 +391,7 @@
margin-left: 5px;
a {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
}
@@ -414,3 +473,102 @@
}
}
}
+
+.time_tracker {
+ padding-bottom: 0;
+ border-bottom: 0;
+
+
+ .sidebar-collapsed-icon {
+
+ > .stopwatch-svg {
+ display: inline-block;
+ }
+
+ svg {
+ width: 16px;
+ height: 16px;
+ fill: $sidebar-collapsed-icon-color;
+ }
+
+ &:hover svg {
+ fill: $gl-text-color;
+ }
+ }
+
+ .help-button,
+ .close-help-button {
+ cursor: pointer;
+ }
+
+ .compare-meter {
+ &.within_estimate {
+ .meter-fill {
+ background: $gl-primary;
+ }
+ }
+
+ &.over_estimate {
+ .meter-fill {
+ background: $red-light;
+ }
+
+ .time-remaining,
+ .compare-value.spent {
+ color: $red-light;
+ }
+ }
+ }
+
+ .meter-container {
+ background: $border-gray-light;
+ border-radius: 3px;
+
+ .meter-fill {
+ max-width: 100%;
+ height: 5px;
+ border-radius: 3px;
+ background: $gl-primary;
+ }
+ }
+
+ .compare-display-container {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 5px;
+
+ .compare-display {
+ font-size: 13px;
+ color: $compare-display-color;
+
+ .compare-value {
+ color: $gl-text-color;
+ }
+ }
+ }
+
+ .time-tracking-help-state {
+ background: $white-light;
+ margin: 16px -20px 0;
+ padding: 16px 20px;
+ border-top: 1px solid $border-gray-light;
+ border-bottom: 1px solid $border-gray-light;
+
+ a:hover {
+ color: $btn-white-active;
+ }
+ }
+
+ .help-state-toggle-enter-active {
+ transition: all .8s ease;
+ }
+
+ .help-state-toggle-leave-active {
+ transition: all .5s ease;
+ }
+
+ .help-state-toggle-enter,
+ .help-state-toggle-leave-active {
+ opacity: 0;
+ }
+}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index d129eb12a45..21d9b4c54ea 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -117,7 +117,7 @@
.manage-labels-list {
.btn-action {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
.fa {
font-size: 18px;
@@ -203,6 +203,10 @@
z-index: 3;
border-radius: $label-border-radius;
padding: 6px 10px 6px 9px;
+
+ &:hover {
+ box-shadow: inset 0 0 0 80px $label-remove-border;
+ }
}
.btn {
diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss
index a7c80dce424..68b6c5ecbd4 100644
--- a/app/assets/stylesheets/pages/lint.scss
+++ b/app/assets/stylesheets/pages/lint.scss
@@ -9,3 +9,13 @@
color: $lint-correct-color;
}
}
+
+.ci-linter {
+ .ci-editor {
+ height: 400px;
+ }
+
+ .ci-template pre {
+ white-space: pre-wrap;
+ }
+}
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 712bd3da22f..71ed5b1361a 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -17,14 +17,19 @@
line-height: 1.5;
p {
- font-size: 18px;
+ font-size: 16px;
color: $login-brand-holder-color;
}
h1:first-child {
font-weight: normal;
- margin-bottom: 30px;
+ margin-bottom: 0.68em;
margin-top: 0;
+ font-size: 34px;
+ }
+
+ h3 {
+ font-size: 22px;
}
img {
diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss
index 36ee5d17211..be7193bae04 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -25,7 +25,7 @@
}
.form-horizontal {
- margin-top: 5px;
+ margin-top: 20px;
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
@@ -98,6 +98,10 @@
padding-right: 35px;
@media (min-width: $screen-sm-min) {
+ width: 250px;
+ }
+
+ @media (min-width: $screen-md-min) {
width: 350px;
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 1b1126695a1..ab68b360f93 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -4,7 +4,7 @@
*/
.mr-state-widget {
background: $gray-light;
- color: $gl-gray;
+ color: $gl-text-color;
border: 1px solid $border-color;
border-radius: 2px;
@@ -58,7 +58,7 @@
padding-right: 0;
a {
- color: $gl-gray;
+ color: $gl-text-color;
}
}
@@ -70,7 +70,7 @@
.ci_widget {
border-bottom: 1px solid $well-inner-border;
- color: $gl-gray;
+ color: $gl-text-color;
svg {
margin-right: 4px;
@@ -94,7 +94,7 @@
}
.normal {
- color: $gl-text-color-dark;
+ color: $gl-text-color;
}
.js-deployment-link {
@@ -106,7 +106,7 @@
font-weight: 600;
font-size: 16px;
margin: 5px 0;
- color: $gl-gray-dark;
+ color: $gl-text-color;
&.has-conflicts .fa-exclamation-triangle {
color: $gl-warning;
@@ -190,7 +190,7 @@
}
.label-branch {
- color: $gl-gray-dark;
+ color: $gl-text-color;
font-family: $monospace_font;
font-weight: bold;
overflow: hidden;
@@ -310,10 +310,6 @@
left: 0;
top: 2px;
}
-
- .commit-row-info {
- line-height: 20px;
- }
}
.btn-clipboard {
@@ -367,7 +363,7 @@
th {
background-color: $white-light;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
}
}
@@ -424,10 +420,6 @@
.merge-request-tabs-holder {
background-color: $white-light;
- .container-limited {
- max-width: $limited-layout-width;
- }
-
&.affix {
top: 100px;
left: 0;
@@ -437,10 +429,26 @@
@media (max-width: $screen-xs-max) {
right: 0;
}
+
+ .merge-request-tabs-container {
+ padding-left: $gl-padding;
+ padding-right: $gl-padding;
+ }
}
+}
- &:not(.affix) .container-fluid {
- padding-left: 0;
- padding-right: 0;
+.limit-container-width {
+ .merge-request-tabs-container {
+ max-width: $limited-layout-width;
+ margin-left: auto;
+ margin-right: auto;
+ }
+}
+
+.limit-container-width:not(.container-limited) {
+ .merge-request-tabs-holder:not(.affix) {
+ .merge-request-tabs-container {
+ max-width: $limited-layout-width - ($gl-padding * 2);
+ }
}
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index f47ae9c6157..686b64cdd24 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -102,13 +102,17 @@
margin-top: 7px;
.issuable-number {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
margin-right: 5px;
}
.avatar {
float: none;
}
+
+ > a:not(:last-of-type) {
+ margin-right: 5px;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index e54e12be82f..f984b469609 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -27,6 +27,7 @@
.new-note,
.note-edit-form {
.note-form-actions {
+ position: relative;
margin-top: $gl-padding;
}
@@ -44,7 +45,7 @@
.note-textarea {
display: block;
padding: 10px 0;
- color: $gl-gray;
+ color: $gl-text-color;
font-family: $regular_font;
border: 0;
@@ -204,7 +205,7 @@
.comment-toolbar {
padding-top: $gl-padding-top;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
border-top: 1px solid $border-color;
}
@@ -265,3 +266,18 @@
}
}
}
+
+.note-edit-warning.settings-message {
+ display: none;
+ padding: 5px 10px;
+ position: absolute;
+ left: 127px;
+ top: 2px;
+
+ @media (max-width: $screen-xs-max) {
+ position: relative;
+ top: 0;
+ left: 0;
+ margin-bottom: 10px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 8f15775ee03..da0caa30c26 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -43,7 +43,7 @@ ul.notes {
}
.system-note-message {
- display: inline-block;
+ display: inline;
&::first-letter {
text-transform: lowercase;
@@ -55,7 +55,7 @@ ul.notes {
}
p {
- display: inline-block;
+ display: inline;
margin: 0;
&::first-letter {
@@ -195,10 +195,10 @@ ul.notes {
}
.note-body {
- overflow: auto;
+ overflow-x: auto;
+ overflow-y: hidden;
.note-text {
- overflow: auto;
word-wrap: break-word;
@include md-typography;
// Reset ul style types since we're nested inside a ul already
@@ -345,7 +345,7 @@ ul.notes {
}
.author_link {
- color: $gl-gray;
+ color: $gl-text-color;
}
}
@@ -353,6 +353,14 @@ ul.notes {
font-size: 14px;
}
+.note-headline-light {
+ display: inline;
+
+ @media (max-width: $screen-xs-min) {
+ display: block;
+ }
+}
+
.note-headline-light,
.discussion-headline-light {
color: $notes-light-color;
@@ -507,7 +515,6 @@ ul.notes {
.line-resolve-all-container {
.btn-group {
- margin-top: -1px;
margin-left: -4px;
}
@@ -518,8 +525,9 @@ ul.notes {
}
.line-resolve-all {
+ vertical-align: middle;
display: inline-block;
- padding: 5px 10px;
+ padding: 6px 10px;
background-color: $gray-light;
border: 1px solid $border-color;
border-radius: $border-radius-default;
@@ -527,18 +535,14 @@ ul.notes {
&.has-next-btn {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
+ border-right: 0;
}
.line-resolve-btn {
- vertical-align: middle;
margin-right: 5px;
}
}
-.line-resolve-text {
- vertical-align: middle;
-}
-
.line-resolve-btn {
display: inline-block;
position: relative;
@@ -580,13 +584,11 @@ ul.notes {
// Merge request notes in diffs
.diff-file {
-
// Diff is side by side
.notes_content.parallel .note-header .note-headline-light {
display: block;
position: relative;
}
-
// Diff is inline
.notes_content .note-header .note-headline-light {
display: inline-block;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index c370b6247b0..47dfc22d533 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -1,4 +1,9 @@
.pipelines {
+ .realtime-loading {
+ font-size: 40px;
+ text-align: center;
+ }
+
.stage {
max-width: 90px;
width: 90px;
@@ -24,6 +29,10 @@
min-width: 1200px;
table-layout: fixed;
+ .label {
+ margin-bottom: 3px;
+ }
+
.pipeline-id {
color: $black;
}
@@ -35,8 +44,8 @@
.pipeline-info,
.pipeline-commit,
- .pipeline-actions,
- .pipeline-stages {
+ .pipeline-stages,
+ .pipeline-actions {
width: 20%;
}
}
@@ -131,7 +140,7 @@
height: 14px;
width: 14px;
vertical-align: middle;
- fill: $gl-gray-light;
+ fill: $gl-text-color-secondary;
}
.fa {
@@ -176,13 +185,16 @@
.stage-cell {
font-size: 0;
+ padding: 10px 4px;
- svg {
- height: 18px;
- width: 18px;
- position: relative;
+ > .stage-container > div > button > span > svg,
+ > .stage-container > button > svg {
+ height: 22px;
+ width: 22px;
+ position: absolute;
+ top: -1px;
+ left: -1px;
z-index: 2;
- vertical-align: middle;
overflow: visible;
}
@@ -195,10 +207,14 @@
white-space: nowrap;
}
+ .tooltip-inner {
+ padding: 3px 4px;
+ }
+
&:not(:last-child) {
&::after {
content: '';
- width: 8px;
+ width: 7px;
position: absolute;
right: -7px;
top: 10px;
@@ -210,7 +226,7 @@
.duration,
.finished-at {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
margin: 4px 0;
.fa {
@@ -231,7 +247,7 @@
.btn {
margin: 0;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
.cancel-retry-btns {
@@ -244,10 +260,10 @@
.dropdown-toggle,
.dropdown-menu {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
.fa {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
font-size: 14px;
}
@@ -276,12 +292,16 @@
}
}
}
+
+ .tooltip {
+ white-space: nowrap;
+ }
}
.build-link {
a {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
}
@@ -337,14 +357,16 @@
padding: $gl-padding;
white-space: nowrap;
transition: max-height 0.3s, padding 0.3s;
+ overflow: auto;
- ul {
+ .stage-column-list,
+ .builds-container > ul {
padding: 0;
}
a {
text-decoration: none;
- color: $gl-text-color-light;
+ color: $gl-text-color-secondary;
}
svg {
@@ -468,13 +490,54 @@
width: 186px;
margin-bottom: 10px;
white-space: normal;
- color: $gl-text-color-light;
+ color: $gl-text-color-secondary;
+
+ // Action Icons in big pipeline-graph nodes
+ > .ci-action-icon-container .ci-action-icon-wrapper {
+ i {
+ color: $border-color;
+ border-radius: 100%;
+ border: 1px solid $border-color;
+ padding: 5px 6px;
+ font-size: 13px;
+ background: $white-light;
+ height: 30px;
+ width: 30px;
+
+ &::before {
+ position: relative;
+ top: 3px;
+ left: 3px;
+ }
+
+ &:hover {
+ color: $gl-text-color;
+ background-color: $stage-hover-bg;
+ border: 1px solid $stage-hover-bg;
+ }
+ }
+
+ .ci-play-icon {
+ padding: 5px 5px 5px 7px;
+ }
+ }
+
+ > .ci-action-icon-container {
+ position: absolute;
+ right: 5px;
+ top: 5px;
+ }
+
+ .ci-status-icon svg {
+ height: 20px;
+ width: 20px;
+ }
.dropdown-menu-toggle {
background-color: transparent;
border: none;
padding: 0;
- color: $gl-text-color-light;
+ color: $gl-text-color-secondary;
&:focus {
outline: none;
@@ -504,16 +567,6 @@
}
}
- > .ci-action-icon-container {
- position: absolute;
- right: 5px;
- top: 5px;
- }
-
- .ci-status-icon svg {
- height: 20px;
- width: 20px;
- }
.arrow {
&::before,
@@ -596,29 +649,9 @@
}
}
}
-
- .grouped-pipeline-dropdown {
- .dropdown-build {
- .build-content {
- width: 100%;
-
- &:hover {
- background-color: $stage-hover-bg;
- color: $gl-text-color;
- }
- }
-
- .ci-action-icon-container {
- padding: 0;
- font-size: 11px;
- position: absolute;
- top: 1px;
- right: 8px;
- }
- }
- }
}
+// Triggers the dropdown in the big pipeline graph
.dropdown-counter-badge {
color: $border-color;
font-weight: 100;
@@ -628,66 +661,6 @@
top: 8px;
}
-.grouped-pipeline-dropdown {
- padding: 0;
- width: 191px;
- min-width: 191px;
- left: auto;
- right: -195px;
- top: -4px;
- box-shadow: 0 1px 5px $black-transparent;
-
- a {
- display: inline-block;
- }
-
- .dropdown-build {
- .build-content {
- width: 100%;
-
- &:hover {
- background-color: $stage-hover-bg;
- color: $gl-text-color;
- }
- }
-
- .ci-action-icon-container {
- padding: 0;
- font-size: 11px;
- position: absolute;
- margin-top: 3px;
- right: 7px;
- }
- }
-
- ul {
- max-height: 245px;
- overflow: auto;
- margin: 3px 0;
-
- li {
- margin: 4px 8px 4px 9px;
- padding: 0;
- line-height: 1.1;
- position: relative;
-
- .ci-action-icon-container:hover {
- background-color: transparent;
- }
-
- .ci-status-icon {
- position: relative;
- top: 2px;
- }
- }
- }
-}
-
-.pipeline-graph .dropdown-build .ci-status-icon svg {
- width: 18px;
- height: 18px;
-}
-
.ci-status-text {
max-width: 110px;
white-space: nowrap;
@@ -699,177 +672,233 @@
font-weight: 200;
}
-// Action Icons
-.ci-action-icon-container .ci-action-icon-wrapper {
- i {
- color: $border-color;
- border-radius: 100%;
- border: 1px solid $border-color;
- padding: 5px 6px;
- font-size: 13px;
- background: $white-light;
- height: 30px;
- width: 30px;
+// Dropdown button in mini pipeline graph
+.mini-pipeline-graph-dropdown-toggle {
+ border-radius: 100px;
+ background-color: $white-light;
+ border-width: 1px;
+ border-style: solid;
+ width: 22px;
+ height: 22px;
+ margin: 0;
+ padding: 0;
+ transition: all 0.2s linear;
+ position: relative;
- &::before {
- position: relative;
- top: 3px;
- left: 3px;
- }
+ > .fa.fa-caret-down {
+ position: absolute;
+ left: 20px;
+ top: 5px;
+ display: inline-block;
+ visibility: hidden;
+ opacity: 0;
+ color: inherit;
+ font-size: 12px;
+ transition: visibility 0.1s, opacity 0.1s linear;
+ }
- &:hover {
- color: $gl-text-color;
- background-color: $stage-hover-bg;
- border: 1px solid $stage-hover-bg;
+ &:active,
+ &:focus,
+ &:hover {
+ outline: none;
+ width: 35px;
+
+ .fa.fa-caret-down {
+ visibility: visible;
+ opacity: 1;
}
}
- .ci-play-icon {
- padding: 5px 5px 5px 7px;
+ // Dropdown button animation in mini pipeline graph
+ &.ci-status-icon-success {
+ border-color: $gl-success;
+ color: $gl-success;
+
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: rgba($gl-success, 0.1);
+ border-color: $gl-success;
+ }
}
-}
-.dropdown-build {
- color: $gl-text-color-light;
+ &.ci-status-icon-failed {
+ border-color: $gl-danger;
+ color: $gl-danger;
- .build-content {
- padding: 4px 7px 8px;
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: rgba($gl-danger, 0.1);
+ border-color: $gl-danger;
+ }
}
- .ci-action-icon-container {
- padding: 0;
- font-size: 11px;
- float: right;
- margin-top: 3px;
- display: inline-block;
- position: relative;
+ &.ci-status-icon-pending,
+ &.ci-status-icon-success_with_warnings {
+ border-color: $gl-warning;
+ color: $gl-warning;
- i {
- font-size: 11px;
- margin-top: 0;
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: rgba($gl-warning, 0.1);
+ border-color: $gl-warning;
}
}
- .ci-action-icon-container {
- i {
- width: 24px;
- height: 24px;
+ &.ci-status-icon-running {
+ border-color: $blue-normal;
+ color: $blue-normal;
- &::before {
- top: 1px;
- left: 1px;
- }
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: rgba($blue-normal, 0.1);
+ border-color: $blue-normal;
}
}
- .stage {
- max-width: 100px;
- width: 100px;
- }
+ &.ci-status-icon-canceled,
+ &.ci-status-icon-disabled,
+ &.ci-status-icon-not-found,
+ &.ci-status-icon-manual {
+ border-color: $gl-text-color;
+ color: $gl-text-color;
- .ci-status-icon svg {
- height: 18px;
- width: 18px;
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: rgba($gl-text-color, 0.1);
+ border-color: $gl-text-color;
+ }
}
- .ci-status-text {
- max-width: 95px;
+ &.ci-status-icon-created,
+ &.ci-status-icon-skipped {
+ border-color: $gray-darkest;
+ color: $gray-darkest;
+
+ &:hover,
+ &:focus,
+ &:active {
+ background-color: rgba($gray-darkest, 0.1);
+ border-color: $gray-darkest;
+ }
}
}
-/**
- * Builds dropdown in mini pipeline
- */
-.mini-pipeline-graph {
- .builds-dropdown {
- background-color: transparent;
- padding: 0;
- color: $gl-text-color-light;
- border: none;
- margin: 0;
-
- &:focus,
- &:hover {
- outline: none;
- margin-right: -8px;
+// dropdown content for big and mini pipeline
+.big-pipeline-graph-dropdown-menu,
+.mini-pipeline-graph-dropdown-menu {
+ width: 195px;
+ max-width: 195px;
- .ci-status-icon {
- width: 32px;
- padding: 0 8px 0 0;
- transition: width 0.1s cubic-bezier(0.25, 0, 1, 1);
+ li {
+ padding: 2px 3px;
+ }
- + .dropdown-caret {
- visibility: visible;
- opacity: 1;
- }
- }
- }
+ .scrollable-menu {
+ max-height: 245px;
+ overflow: auto;
+ }
- &:focus,
- &:active {
- .ci-status-icon-success {
- background-color: rgba($gl-success, .1);
- }
+ // Loading icon
+ .builds-dropdown-loading {
+ margin: 0 auto;
+ width: 20px;
+ }
+
+ // Action icon on the right
+ a.ci-action-icon-wrapper {
+ color: $action-icon-color;
+ border: 1px solid $action-icon-color;
+ border-radius: 20px;
+ width: 22px;
+ height: 22px;
+ padding: 2px 0 0 5px;
+ cursor: pointer;
+ float: right;
+ margin: -26px 9px 0 0;
+ font-size: 12px;
+ background-color: $white-light;
- .ci-status-icon-failed {
- background-color: rgba($gl-danger, .1);
- }
+ &:hover,
+ &:focus {
+ text-decoration: none;
+ color: $gl-text-color;
+ background-color: $stage-hover-bg;
+ border: 1px solid transparent;
+ }
+ }
- .ci-status-icon-pending,
- .ci-status-icon-success_with_warnings {
- background-color: rgba($gl-warning, .1);
- }
+ // link to the build
+ .mini-pipeline-graph-dropdown-item {
+ padding: 3px 7px 4px;
+ clear: both;
+ font-weight: normal;
+ line-height: 1.428571429;
+ white-space: nowrap;
+ margin: 0 5px;
+ border-radius: 3px;
- .ci-status-icon-running {
- background-color: rgba($blue-normal, .1);
- }
+ // build name
+ .ci-build-text {
+ font-weight: 200;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ width: 90px;
+ color: $gl-text-color-secondary;
+ margin-left: 2px;
+ display: inline-block;
+ top: 1px;
+ vertical-align: text-bottom;
+ position: relative;
+ }
- .ci-status-icon-canceled,
- .ci-status-icon-disabled,
- .ci-status-icon-not-found {
- background-color: rgba($gl-gray, .1);
- }
+ // status icon on the left
+ .ci-status-icon {
+ top: 3px;
+ position: relative;
- .ci-status-icon-created,
- .ci-status-icon-skipped {
- background-color: rgba($gray-darkest, .1);
+ > svg {
+ overflow: visible;
+ width: 18px;
+ height: 18px;
}
}
- .mini-pipeline-graph-icon-container {
- .dropdown-caret {
- font-size: 11px;
- position: absolute;
- top: 6px;
- left: 20px;
- margin-right: -6px;
- z-index: 2;
- visibility: hidden;
- opacity: 0;
- transition: visibility 0.1s, opacity 0.1s linear;
- }
+ &:hover,
+ &:focus {
+ outline: none;
+ text-decoration: none;
+ color: $gl-text-color;
+ background-color: $stage-hover-bg;
}
}
+}
- .dropdown-build .build-content {
- padding: 3px 7px 7px;
- }
-
- .builds-dropdown-loading {
- margin: 10px auto;
- width: 18px;
- }
-
- .grouped-pipeline-dropdown {
- right: -172px;
- top: 23px;
- min-height: 50px;
+// Dropdown in the big pipeline graph
+.big-pipeline-graph-dropdown-menu {
+ width: 195px;
+ min-width: 195px;
+ left: auto;
+ right: -195px;
+ top: -4px;
+ box-shadow: 0 1px 5px $black-transparent;
- a {
- color: $gl-text-color-light;
+ .mini-pipeline-graph-dropdown-item {
+ .ci-status-icon {
+ top: -1px;
}
}
+}
+/**
+ * Top arrow in the dropdown in the mini pipeline graph
+ */
+.mini-pipeline-graph-dropdown-menu {
.arrow-up {
&::before,
&::after {
@@ -898,31 +927,8 @@
}
/**
- * Icons in mini pipeline graph
+ * Terminal
*/
-.mini-pipeline-graph-icon-container .ci-status-icon {
- display: inline-block;
- border: 1px solid;
- border-radius: 22px;
- margin-right: 1px;
- width: 22px;
- height: 22px;
- position: relative;
- z-index: 2;
- transition: all 0.1s cubic-bezier(0.25, 0, 1, 1);
-
- svg {
- top: -1px;
- left: -1px;
- }
-}
-
-.stage-cell .mini-pipeline-graph-icon-container .ci-status-icon svg {
- width: 22px;
- height: 22px;
-}
-
-
.terminal-icon {
margin-left: 3px;
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 8b1976bd925..722b3006f7c 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -216,8 +216,8 @@
}
}
-.user-profile {
+.user-profile {
.cover-controls a {
margin-left: 5px;
}
@@ -231,8 +231,11 @@
}
}
- @media (max-width: $screen-xs-max) {
+ .user-profile-nav {
+ font-size: 0;
+ }
+ @media (max-width: $screen-xs-max) {
.cover-block {
padding-top: 20px;
}
@@ -253,6 +256,12 @@
}
}
}
+
+ .user-profile-nav {
+ a {
+ margin-right: 0;
+ }
+ }
}
}
@@ -271,4 +280,4 @@ table.u2f-registrations {
.scopes-list {
padding-left: 18px;
}
-} \ No newline at end of file
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index b99be02ab0c..8b59c20cb65 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -9,24 +9,24 @@
.new_project,
.edit-project {
- fieldset {
-
- &.features {
+ .sharing-and-permissions {
+ .header {
+ padding-top: $gl-vert-padding;
+ }
- .label-light {
- margin-bottom: 0;
- }
+ .label-light {
+ margin-bottom: 0;
+ }
- .help-block {
- margin-top: 0;
- }
+ .help-block {
+ margin-top: 0;
}
.form-group {
margin-bottom: 5px;
}
- &> .form-group {
+ & > .form-group {
padding-left: 0;
}
}
@@ -73,7 +73,7 @@
border: 1px solid $border-color;
}
- &+ .select2 a {
+ & + .select2 a {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
@@ -292,7 +292,7 @@
.option-title {
font-weight: normal;
display: inline-block;
- color: $gl-gray-dark;
+ color: $gl-text-color;
}
.option-descr {
@@ -331,7 +331,7 @@
a.deploy-project-label {
padding: 5px;
margin-right: 5px;
- color: $gl-gray;
+ color: $gl-text-color;
background-color: $row-hover;
&:hover {
@@ -372,7 +372,7 @@ a.deploy-project-label {
}
a {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
.dropdown-menu {
@@ -426,7 +426,7 @@ a.deploy-project-label {
width: 100%;
height: 100%;
padding-top: $gl-padding;
- color: $gl-gray;
+ color: $gl-text-color;
.caption {
min-height: 30px;
@@ -523,7 +523,7 @@ a.deploy-project-label {
&:hover,
&:focus {
- color: darken($notes-light-color, 15%);
+ color: $gl-text-color;
}
}
@@ -552,7 +552,7 @@ pre.light-well {
margin: 0 7px 7px;
h5 {
- color: $gl-text-color-dark;
+ color: $gl-text-color;
}
.light-well {
@@ -587,11 +587,21 @@ pre.light-well {
.project-full-name {
@include str-truncated;
+
+ @media (max-width: $screen-xs-max) {
+ max-width: 50%;
+ }
}
.controls {
line-height: $list-text-height;
+ .badge {
+ @media (max-width: $screen-xs-max) {
+ display: none;
+ }
+ }
+
a:hover {
text-decoration: none;
}
@@ -605,6 +615,12 @@ pre.light-well {
top: 2px;
}
}
+
+ .description p {
+ @media (max-width: $screen-xs-max) {
+ max-width: 50%;
+ }
+ }
}
.bottom {
@@ -618,7 +634,6 @@ pre.light-well {
margin: 0;
}
-
.activity-filter-block {
.controls {
padding-bottom: 7px;
@@ -663,7 +678,7 @@ pre.light-well {
}
.commit-row-message {
- color: $gl-gray;
+ color: $gl-text-color;
}
.commit_short_id {
@@ -751,7 +766,7 @@ pre.light-well {
.protected-branches-list {
a {
- color: $gl-gray;
+ color: $gl-text-color;
&:hover {
color: $gl-link-color;
@@ -811,7 +826,31 @@ pre.light-well {
.compare-form-group {
.dropdown-menu {
- width: 300px;
+ width: 100%;
+
+ @media (min-width: $screen-sm-min) {
+ width: 300px;
+ }
+ }
+
+ + .compare-ellipsis {
+ width: 100%;
+ vertical-align: middle;
+ text-align: center;
+ margin-top: -20px;
+
+ @media (min-width: $screen-sm-min) {
+ margin-top: 0;
+ width: auto;
+ }
+ }
+
+ .inline-input-group {
+ width: 100%;
+
+ @media (min-width: $screen-sm-min) {
+ width: 250px;
+ }
}
}
@@ -866,10 +905,18 @@ pre.light-well {
}
}
-.project-feature-nested {
+.project-feature {
+ padding-top: 10px;
+
@media (min-width: $screen-sm-min) {
padding-left: 45px;
}
+
+ &.nested {
+ @media (min-width: $screen-sm-min) {
+ padding-left: 90px;
+ }
+ }
}
.project-repo-select {
@@ -882,8 +929,32 @@ pre.light-well {
.variables-table {
table-layout: fixed;
+ &.table-responsive {
+ border: none;
+ }
+
.variable-key {
- width: 30%;
+ width: 300px;
+ max-width: 300px;
+ overflow: hidden;
+ word-wrap: break-word;
+
+ // override bootstrap
+ white-space: normal!important;
+
+ @media (max-width: $screen-sm-max) {
+ width: 150px;
+ max-width: 150px;
+ }
+ }
+
+ .variable-value {
+ @media(max-width: $screen-xs-max) {
+ width: 150px;
+ max-width: 150px;
+ overflow: hidden;
+ word-wrap: break-word;
+ }
}
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index cedd4cb2987..88ea92c5afb 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -14,6 +14,19 @@
}
}
+.search form:hover,
+.file-finder-input:hover,
+.issuable-search-form:hover,
+.search-text-input:hover,
+.form-control:hover {
+ border-color: lighten($dropdown-input-focus-border, 20%);
+ box-shadow: 0 0 4px lighten($search-input-focus-shadow-color, 20%);
+}
+
+input[type="checkbox"]:hover {
+ box-shadow: 0 0 2px 2px lighten($search-input-focus-shadow-color, 20%), 0 0 0 1px lighten($search-input-focus-shadow-color, 20%);
+}
+
.search {
margin-right: 10px;
margin-left: 10px;
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index ddee2c95247..a28a87ed4f8 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -1,5 +1,5 @@
.settings-list-icon {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
font-size: $settings-icon-size;
line-height: 42px;
}
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index 4acd17360c1..6f31d4ed789 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -19,7 +19,8 @@
overflow: visible;
}
- &.ci-failed {
+ &.ci-failed,
+ &.ci-failed_with_warnings {
color: $gl-danger;
border-color: $gl-danger;
@@ -61,15 +62,15 @@
&.ci-canceled,
&.ci-disabled {
- color: $gl-gray;
- border-color: $gl-gray;
+ color: $gl-text-color;
+ border-color: $gl-text-color;
&:not(span):hover {
- background-color: rgba($gl-gray, .07);
+ background-color: rgba($gl-text-color, .07);
}
svg {
- fill: $gl-gray;
+ fill: $gl-text-color;
}
}
@@ -101,15 +102,15 @@
&.ci-created,
&.ci-skipped {
- color: $gl-gray-light;
- border-color: $gl-gray-light;
+ color: $gl-text-color-secondary;
+ border-color: $gl-text-color-secondary;
&:not(span):hover {
- background-color: rgba($gl-gray-light, .07);
+ background-color: rgba($gl-text-color-secondary, .07);
}
svg {
- fill: $gl-gray-light;
+ fill: $gl-text-color-secondary;
}
}
@@ -135,3 +136,9 @@
left: 5px;
}
}
+
+.ci-status-link {
+ svg {
+ overflow: visible;
+ }
+}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 508b30f3947..01675acc62e 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -90,7 +90,7 @@
}
p {
- color: $gl-text-color-dark;
+ color: $gl-text-color;
}
}
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index cad4e149845..948921efc0b 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -32,6 +32,10 @@
.last-commit {
@include str-truncated(506px);
+ .fa-angle-right {
+ margin-left: 5px;
+ }
+
@media (min-width: $screen-md-min) and (max-width: $screen-md-max) {
@include str-truncated(450px);
}
@@ -78,7 +82,7 @@
i,
a {
- color: $gl-dark-link-color;
+ color: $gl-text-color;
}
img {
@@ -104,21 +108,21 @@
padding-right: 8px;
.commit-author-name {
- color: $gl-gray;
+ color: $gl-text-color;
}
}
.tree-time-ago {
min-width: 135px;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
.tree-commit {
max-width: 320px;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
.tree-commit-link {
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
&:hover {
text-decoration: underline;
@@ -134,21 +138,18 @@
.blob-commit-info {
list-style: none;
- padding: $gl-padding;
background: $gray-light;
+ padding: 6px 0;
border: 1px solid $border-color;
border-bottom: none;
margin: 0;
- .commit {
- padding-top: 0;
- padding-bottom: 0;
+ .table-list-cell {
+ border-bottom: none;
+ }
- .commit-row-title {
- .commit-row-message {
- font-weight: normal;
- }
- }
+ .commit-actions {
+ width: 200px;
}
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 480cb2b9f0d..9bc47bbe173 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -29,7 +29,7 @@
.wiki-last-edit-by {
display: block;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
strong {
color: $gl-text-color;
@@ -38,7 +38,7 @@
.light {
font-weight: normal;
- color: $gl-gray-light;
+ color: $gl-text-color-secondary;
}
.git-access-header {
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index c2bb8464824..543d5eac504 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -5,7 +5,11 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def update
- if @application_setting.update_attributes(application_setting_params)
+ successful = ApplicationSettings::UpdateService
+ .new(@application_setting, current_user, application_setting_params)
+ .execute
+
+ if successful
redirect_to admin_application_settings_path,
notice: 'Application settings saved successfully'
else
@@ -67,69 +71,78 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.require(:application_setting).permit(
- :default_projects_limit,
- :default_branch_protection,
- :signup_enabled,
- :signin_enabled,
- :require_two_factor_authentication,
- :two_factor_grace_period,
- :gravatar_enabled,
- :sign_in_text,
- :after_sign_up_text,
- :help_page_text,
- :home_page_url,
+ application_setting_params_ce
+ )
+ end
+
+ def application_setting_params_ce
+ [
+ :admin_notification_email,
:after_sign_out_path,
- :max_attachment_size,
- :session_expire_delay,
+ :after_sign_up_text,
+ :akismet_api_key,
+ :akismet_enabled,
+ :container_registry_token_expire_delay,
+ :default_branch_protection,
+ :default_group_visibility,
:default_project_visibility,
+ :default_projects_limit,
:default_snippet_visibility,
- :default_group_visibility,
- :domain_whitelist_raw,
:domain_blacklist_enabled,
- :domain_blacklist_raw,
:domain_blacklist_file,
- :version_check_enabled,
- :admin_notification_email,
- :user_oauth_applications,
- :user_default_external,
- :shared_runners_enabled,
- :shared_runners_text,
+ :domain_blacklist_raw,
+ :domain_whitelist_raw,
+ :email_author_in_body,
+ :enabled_git_access_protocol,
+ :gravatar_enabled,
+ :help_page_text,
+ :home_page_url,
+ :housekeeping_bitmaps_enabled,
+ :housekeeping_enabled,
+ :housekeeping_full_repack_period,
+ :housekeeping_gc_period,
+ :housekeeping_incremental_repack_period,
+ :html_emails_enabled,
+ :koding_enabled,
+ :koding_url,
+ :plantuml_enabled,
+ :plantuml_url,
:max_artifacts_size,
+ :max_attachment_size,
:metrics_enabled,
:metrics_host,
- :metrics_port,
- :metrics_pool_size,
- :metrics_timeout,
:metrics_method_call_threshold,
+ :metrics_packet_size,
+ :metrics_pool_size,
+ :metrics_port,
:metrics_sample_interval,
+ :metrics_timeout,
:recaptcha_enabled,
- :recaptcha_site_key,
:recaptcha_private_key,
- :sentry_enabled,
- :sentry_dsn,
- :akismet_enabled,
- :akismet_api_key,
- :koding_enabled,
- :koding_url,
- :email_author_in_body,
- :html_emails_enabled,
+ :recaptcha_site_key,
:repository_checks_enabled,
- :metrics_packet_size,
+ :require_two_factor_authentication,
+ :session_expire_delay,
+ :sign_in_text,
+ :signin_enabled,
+ :signup_enabled,
+ :sentry_dsn,
+ :sentry_enabled,
:send_user_confirmation_email,
- :container_registry_token_expire_delay,
- :enabled_git_access_protocol,
+ :shared_runners_enabled,
+ :shared_runners_text,
:sidekiq_throttling_enabled,
:sidekiq_throttling_factor,
- :housekeeping_enabled,
- :housekeeping_bitmaps_enabled,
- :housekeeping_incremental_repack_period,
- :housekeeping_full_repack_period,
- :housekeeping_gc_period,
+ :two_factor_grace_period,
+ :user_default_external,
+ :user_oauth_applications,
+ :version_check_enabled,
+
+ disabled_oauth_sign_in_sources: [],
+ import_sources: [],
repository_storages: [],
restricted_visibility_levels: [],
- import_sources: [],
- disabled_oauth_sign_in_sources: [],
sidekiq_throttling_queues: []
- )
+ ]
end
end
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index add1c819adf..b7722a1d15d 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -61,7 +61,11 @@ class Admin::GroupsController < Admin::ApplicationController
end
def group_params
- params.require(:group).permit(
+ params.require(:group).permit(group_params_ce)
+ end
+
+ def group_params_ce
+ [
:avatar,
:description,
:lfs_enabled,
@@ -69,6 +73,6 @@ class Admin::GroupsController < Admin::ApplicationController
:path,
:request_access_enabled,
:visibility_level
- )
+ ]
end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index df9039b16b2..aa0f8d434dc 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -161,15 +161,6 @@ class Admin::UsersController < Admin::ApplicationController
@user ||= User.find_by!(username: params[:id])
end
- def user_params
- params.require(:user).permit(
- :email, :remember_me, :bio, :name, :username,
- :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password,
- :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, :hide_no_password,
- :projects_limit, :can_create_group, :admin, :key_id, :external
- )
- end
-
def redirect_back_or_admin_user(options = {})
redirect_back_or_default(default: default_route, options: options)
end
@@ -177,4 +168,36 @@ class Admin::UsersController < Admin::ApplicationController
def default_route
[:admin, @user]
end
+
+ def user_params
+ params.require(:user).permit(user_params_ce)
+ end
+
+ def user_params_ce
+ [
+ :admin,
+ :avatar,
+ :bio,
+ :can_create_group,
+ :color_scheme_id,
+ :email,
+ :extern_uid,
+ :external,
+ :force_random_password,
+ :hide_no_password,
+ :hide_no_ssh_key,
+ :key_id,
+ :linkedin,
+ :name,
+ :password_expires_at,
+ :projects_limit,
+ :provider,
+ :remember_me,
+ :skype,
+ :theme_id,
+ :twitter,
+ :username,
+ :website_url
+ ]
+ end
end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 5f13353baa1..d7a45bacd35 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -18,15 +18,14 @@ class AutocompleteController < ApplicationController
if params[:search].blank?
# Include current user if available to filter by "Me"
if params[:current_user].present? && current_user
- @users = [*@users, current_user]
+ @users = @users.where.not(id: current_user.id)
+ @users = [current_user, *@users]
end
if params[:author_id].present?
author = User.find_by_id(params[:author_id])
- @users = [author, *@users] if author
+ @users = [author, *@users].uniq if author
end
-
- @users.uniq!
end
render json: @users, only: [:name, :username, :id], methods: [:avatar_url]
diff --git a/app/controllers/concerns/cycle_analytics_params.rb b/app/controllers/concerns/cycle_analytics_params.rb
index 2aaf8f2b451..52e06f4945a 100644
--- a/app/controllers/concerns/cycle_analytics_params.rb
+++ b/app/controllers/concerns/cycle_analytics_params.rb
@@ -1,6 +1,10 @@
module CycleAnalyticsParams
extend ActiveSupport::Concern
+ def options(params)
+ @options ||= { from: start_date(params), current_user: current_user }
+ end
+
def start_date(params)
params[:start_date] == '30' ? 30.days.ago : 90.days.ago
end
diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb
deleted file mode 100644
index 5c503c5b698..00000000000
--- a/app/controllers/concerns/global_milestones.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module GlobalMilestones
- extend ActiveSupport::Concern
-
- def milestones
- epoch = DateTime.parse('1970-01-01')
- @milestones = MilestonesFinder.new.execute(@projects, params)
- @milestones = GlobalMilestone.build_collection(@milestones)
- @milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
- end
-
- def milestone
- milestones = Milestone.of_projects(@projects).where(title: params[:title])
-
- if milestones.present?
- @milestone = GlobalMilestone.new(params[:title], milestones)
- else
- render_404
- end
- end
-end
diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb
index 549a8526715..d7f5a4e4682 100644
--- a/app/controllers/concerns/service_params.rb
+++ b/app/controllers/concerns/service_params.rb
@@ -1,31 +1,72 @@
module ServiceParams
extend ActiveSupport::Concern
- ALLOWED_PARAMS = [:title, :token, :type, :active, :api_key, :api_url, :api_version, :subdomain,
- :room, :recipients, :project_url, :webhook,
- :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
- :build_key, :server, :teamcity_url, :drone_url, :build_type,
- :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel,
- :colorize_messages, :channels,
- # We're using `issues_events` and `merge_requests_events`
- # in the view so we still need to explicitly state them
- # here. `Service#event_names` would only give
- # `issue_events` and `merge_request_events` (singular!)
- # See app/helpers/services_helper.rb for how we
- # make those event names plural as special case.
- :issues_events, :confidential_issues_events, :merge_requests_events,
- :notify_only_broken_builds, :notify_only_broken_pipelines,
- :add_pusher, :send_from_committer_email, :disable_diffs,
- :external_wiki_url, :notify, :color,
- :server_host, :server_port, :default_irc_uri, :enable_ssl_verification,
- :jira_issue_transition_id, :url, :project_key, :ca_pem, :namespace]
+ ALLOWED_PARAMS_CE = [
+ :active,
+ :add_pusher,
+ :api_key,
+ :api_url,
+ :api_version,
+ :bamboo_url,
+ :build_key,
+ :build_type,
+ :ca_pem,
+ :channel,
+ :channels,
+ :color,
+ :colorize_messages,
+ :confidential_issues_events,
+ :default_irc_uri,
+ :description,
+ :device,
+ :disable_diffs,
+ :drone_url,
+ :enable_ssl_verification,
+ :external_wiki_url,
+ # We're using `issues_events` and `merge_requests_events`
+ # in the view so we still need to explicitly state them
+ # here. `Service#event_names` would only give
+ # `issue_events` and `merge_request_events` (singular!)
+ # See app/helpers/services_helper.rb for how we
+ # make those event names plural as special case.
+ :issues_events,
+ :issues_url,
+ :jira_issue_transition_id,
+ :merge_requests_events,
+ :namespace,
+ :new_issue_url,
+ :notify,
+ :notify_only_broken_builds,
+ :notify_only_broken_pipelines,
+ :password,
+ :priority,
+ :project_key,
+ :project_url,
+ :recipients,
+ :restrict_to_branch,
+ :room,
+ :send_from_committer_email,
+ :server,
+ :server_host,
+ :server_port,
+ :sound,
+ :subdomain,
+ :teamcity_url,
+ :title,
+ :token,
+ :type,
+ :url,
+ :user_key,
+ :username,
+ :webhook
+ ]
# Parameters to ignore if no value is specified
FILTER_BLANK_PARAMS = [:password]
def service_params
dynamic_params = @service.event_channel_names + @service.event_names
- service_params = params.permit(:id, service: ALLOWED_PARAMS + dynamic_params)
+ service_params = params.permit(:id, service: ALLOWED_PARAMS_CE + dynamic_params)
if service_params[:service].is_a?(Hash)
FILTER_BLANK_PARAMS.each do |param|
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index 3da44b9b888..306afb65f10 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -14,12 +14,8 @@ class ConfirmationsController < Devise::ConfirmationsController
if signed_in?(resource_name)
after_sign_in_path_for(resource)
else
- sign_in(resource)
- if signed_in?(resource_name)
- after_sign_in_path_for(resource)
- else
- new_session_path(resource_name)
- end
+ flash[:notice] += " Please sign in."
+ new_session_path(resource_name)
end
end
end
diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb
index fa9c6c054f0..7f506db583f 100644
--- a/app/controllers/dashboard/milestones_controller.rb
+++ b/app/controllers/dashboard/milestones_controller.rb
@@ -1,6 +1,4 @@
class Dashboard::MilestonesController < Dashboard::ApplicationController
- include GlobalMilestones
-
before_action :projects
before_action :milestone, only: [:show]
@@ -17,4 +15,15 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
def show
end
+
+ private
+
+ def milestones
+ @milestones = DashboardMilestone.build_collection(@projects, params)
+ end
+
+ def milestone
+ @milestone = DashboardMilestone.build(@projects, params[:title])
+ render_404 unless @milestone
+ end
end
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index a62c6211372..26e17a7553e 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -22,6 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending
@projects = filter_projects(Project.trending)
+ @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page])
respond_to do |format|
diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb
index 24ec4eec3f2..0d872c86c8a 100644
--- a/app/controllers/groups/milestones_controller.rb
+++ b/app/controllers/groups/milestones_controller.rb
@@ -1,6 +1,4 @@
class Groups::MilestonesController < Groups::ApplicationController
- include GlobalMilestones
-
before_action :group_projects
before_action :milestone, only: [:show, :update]
before_action :authorize_admin_milestones!, only: [:new, :create, :update]
@@ -73,4 +71,13 @@ class Groups::MilestonesController < Groups::ApplicationController
def milestone_path(title)
group_milestone_path(@group, title.to_slug.to_s, title: title)
end
+
+ def milestones
+ @milestones = GroupMilestone.build_collection(@group, @projects, params)
+ end
+
+ def milestone
+ @milestone = GroupMilestone.build(@group, @projects, params[:title])
+ render_404 unless @milestone
+ end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index b61f4e9a2db..f81237db991 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -125,7 +125,11 @@ class GroupsController < Groups::ApplicationController
end
def group_params
- params.require(:group).permit(
+ params.require(:group).permit(group_params_ce)
+ end
+
+ def group_params_ce
+ [
:avatar,
:description,
:lfs_enabled,
@@ -135,7 +139,7 @@ class GroupsController < Groups::ApplicationController
:request_access_enabled,
:share_with_group_lock,
:visibility_level
- )
+ ]
end
def load_events
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index fbe391fc58c..886934a3f67 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -94,7 +94,7 @@ class Projects::BuildsController < Projects::ApplicationController
private
def build
- @build ||= project.builds.find_by!(id: params[:id])
+ @build ||= project.builds.find_by!(id: params[:id]).present(current_user: current_user)
end
def build_path(build)
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 791ed88db30..bfc59bcc862 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -12,7 +12,6 @@ class Projects::CommitController < Projects::ApplicationController
before_action :authorize_read_pipeline!, only: [:pipelines]
before_action :commit
before_action :define_commit_vars, only: [:show, :diff_for_path, :pipelines]
- before_action :define_status_vars, only: [:show, :pipelines]
before_action :define_note_vars, only: [:show, :diff_for_path]
before_action :authorize_edit_tree!, only: [:revert, :cherry_pick]
@@ -106,10 +105,6 @@ class Projects::CommitController < Projects::ApplicationController
}
end
- def define_status_vars
- @ci_pipelines = project.pipelines.where(sha: commit.sha)
- end
-
def assign_change_commit_vars(mr_source_branch)
@commit = project.commit(params[:id])
@target_branch = params[:target_branch]
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index ec02fc15d35..d32966645c8 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -25,8 +25,17 @@ class Projects::CompareController < Projects::ApplicationController
end
def create
- redirect_to namespace_project_compare_path(@project.namespace, @project,
+ if params[:from].blank? || params[:to].blank?
+ flash[:alert] = "You must select from and to branches"
+ from_to_vars = {
+ from: params[:from].presence,
+ to: params[:to].presence
+ }
+ redirect_to namespace_project_compare_index_path(@project.namespace, @project, from_to_vars)
+ else
+ redirect_to namespace_project_compare_path(@project.namespace, @project,
params[:from], params[:to])
+ end
end
private
diff --git a/app/controllers/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb
index 13b3eec761f..b69d46f2c41 100644
--- a/app/controllers/projects/cycle_analytics/events_controller.rb
+++ b/app/controllers/projects/cycle_analytics/events_controller.rb
@@ -9,56 +9,52 @@ module Projects
before_action :authorize_read_merge_request!, only: [:code, :review]
def issue
- render_events(events.issue_events)
+ render_events(cycle_analytics[:issue].events)
end
def plan
- render_events(events.plan_events)
+ render_events(cycle_analytics[:plan].events)
end
def code
- render_events(events.code_events)
+ render_events(cycle_analytics[:code].events)
end
def test
- options[:branch] = events_params[:branch_name]
+ options(events_params)[:branch] = events_params[:branch_name]
- render_events(events.test_events)
+ render_events(cycle_analytics[:test].events)
end
def review
- render_events(events.review_events)
+ render_events(cycle_analytics[:review].events)
end
def staging
- render_events(events.staging_events)
+ render_events(cycle_analytics[:staging].events)
end
def production
- render_events(events.production_events)
+ render_events(cycle_analytics[:production].events)
end
private
-
- def render_events(events_list)
+
+ def render_events(events)
respond_to do |format|
format.html
- format.json { render json: { events: events_list } }
+ format.json { render json: { events: events } }
end
end
- def events
- @events ||= Gitlab::CycleAnalytics::Events.new(project: project, options: options)
- end
-
- def options
- @options ||= { from: start_date(events_params), current_user: current_user }
+ def cycle_analytics
+ @cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params))
end
def events_params
return {} unless params[:events].present?
- params[:events].slice(:start_date, :branch_name)
+ params[:events].permit(:start_date, :branch_name)
end
end
end
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index ac639ef015b..88ac3ad046b 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -6,11 +6,9 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
before_action :authorize_read_cycle_analytics!
def show
- @cycle_analytics = ::CycleAnalytics.new(@project, current_user, from: start_date(cycle_analytics_params))
+ @cycle_analytics = ::CycleAnalytics.new(@project, options(cycle_analytics_params))
- stats_values, cycle_analytics_json = generate_cycle_analytics_data
-
- @cycle_analytics_no_data = stats_values.blank?
+ @cycle_analytics_no_data = @cycle_analytics.no_stats?
respond_to do |format|
format.html
@@ -23,50 +21,14 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
def cycle_analytics_params
return {} unless params[:cycle_analytics].present?
- { start_date: params[:cycle_analytics][:start_date] }
+ params[:cycle_analytics].permit(:start_date)
end
- def generate_cycle_analytics_data
- stats_values = []
-
- cycle_analytics_view_data = [[:issue, "Issue", "Related Issues", "Time before an issue gets scheduled"],
- [:plan, "Plan", "Related Commits", "Time before an issue starts implementation"],
- [:code, "Code", "Related Merge Requests", "Time spent coding"],
- [:test, "Test", "Relative Builds Trigger by Commits", "The time taken to build and test the application"],
- [:review, "Review", "Relative Merged Requests", "The time taken to review the code"],
- [:staging, "Staging", "Relative Deployed Builds", "The time taken in staging"],
- [:production, "Production", "Related Issues", "The total time taken from idea to production"]]
-
- stats = cycle_analytics_view_data.reduce([]) do |stats, (stage_method, stage_text, stage_legend, stage_description)|
- value = @cycle_analytics.send(stage_method).presence
-
- stats_values << value.abs if value
-
- stats << {
- title: stage_text,
- description: stage_description,
- legend: stage_legend,
- value: value && !value.zero? ? distance_of_time_in_words(value) : nil
- }
-
- stats
- end
-
- issues = @cycle_analytics.summary.new_issues
- commits = @cycle_analytics.summary.commits
- deploys = @cycle_analytics.summary.deploys
-
- summary = [
- { title: "New Issue".pluralize(issues), value: issues },
- { title: "Commit".pluralize(commits), value: commits },
- { title: "Deploy".pluralize(deploys), value: deploys }
- ]
-
- cycle_analytics_hash = { summary: summary,
- stats: stats,
- permissions: @cycle_analytics.permissions(user: current_user)
+ def cycle_analytics_json
+ {
+ summary: @cycle_analytics.summary,
+ stats: @cycle_analytics.stats,
+ permissions: @cycle_analytics.permissions(user: current_user)
}
-
- [stats_values, cycle_analytics_hash]
end
end
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 8714349e27f..70845617d3c 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -109,12 +109,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end
def repository
+ wiki? ? project.wiki.repository : project.repository
+ end
+
+ def wiki?
+ return @wiki if defined?(@wiki)
+
_, suffix = project_id_with_suffix
- if suffix == '.wiki.git'
- project.wiki.repository
- else
- project.repository
- end
+ @wiki = suffix == '.wiki.git'
end
def render_not_found
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 9184dcccac5..278098fcc58 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -84,7 +84,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
end
def access
- @access ||= Gitlab::GitAccess.new(user, project, 'http', authentication_abilities: authentication_abilities)
+ @access ||= access_klass.new(user, project, 'http', authentication_abilities: authentication_abilities)
end
def access_check
@@ -102,4 +102,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController
access_check.allowed?
end
+
+ def access_klass
+ @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
+ end
end
diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb
index 9eaf26a0dbf..66b7bdbd988 100644
--- a/app/controllers/projects/group_links_controller.rb
+++ b/app/controllers/projects/group_links_controller.rb
@@ -4,10 +4,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
before_action :authorize_admin_project_member!, only: [:update]
def index
- @group_links = project.project_group_links.all
-
- @skip_groups = @group_links.pluck(:group_id)
- @skip_groups << project.namespace_id unless project.personal?
+ redirect_to namespace_project_settings_members_path
end
def create
@@ -25,7 +22,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
flash[:alert] = 'Please select a group.'
end
- redirect_to namespace_project_group_links_path(project.namespace, project)
+ redirect_to namespace_project_settings_members_path(project.namespace, project)
end
def update
@@ -39,7 +36,7 @@ class Projects::GroupLinksController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to namespace_project_group_links_path(project.namespace, project)
+ redirect_to namespace_project_settings_members_path(project.namespace, project)
end
format.js { head :ok }
end
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 0ae8ff98009..b668a9331e7 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -6,21 +6,15 @@ class Projects::HooksController < Projects::ApplicationController
layout "project_settings"
- def index
- @hooks = @project.hooks
- @hook = ProjectHook.new
- end
-
def create
@hook = @project.hooks.new(hook_params)
@hook.save
- if @hook.valid?
- redirect_to namespace_project_hooks_path(@project.namespace, @project)
- else
+ unless @hook.valid?
@hooks = @project.hooks.select(&:persisted?)
- render :index
+ flash[:alert] = @hook.errors.full_messages.join.html_safe
end
+ redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
end
def test
@@ -44,7 +38,7 @@ class Projects::HooksController < Projects::ApplicationController
def destroy
hook.destroy
- redirect_to namespace_project_hooks_path(@project.namespace, @project)
+ redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
end
private
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 2beb0df8a07..8472ceca329 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -33,6 +33,18 @@ class Projects::IssuesController < Projects::ApplicationController
@labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute
end
+ @users = []
+
+ if params[:assignee_id].present?
+ assignee = User.find_by_id(params[:assignee_id])
+ @users.push(assignee) if assignee
+ end
+
+ if params[:author_id].present?
+ author = User.find_by_id(params[:author_id])
+ @users.push(author) if author
+ end
+
respond_to do |format|
format.html
format.atom { render layout: false }
diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb
index d87dff2a80e..01d99c7df35 100644
--- a/app/controllers/projects/mattermosts_controller.rb
+++ b/app/controllers/projects/mattermosts_controller.rb
@@ -30,7 +30,7 @@ class Projects::MattermostsController < Projects::ApplicationController
def configure_params
params.require(:mattermost).permit(:trigger, :team_id).merge(
url: service_trigger_url(@service),
- icon_url: asset_url('gitlab_logo.png'))
+ icon_url: asset_url('slash-command-logo.png'))
end
def teams
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index fc8a289d49d..9ac5bf4b9f8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -98,7 +98,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@start_version = @comparable_diffs.find { |diff| diff.head_commit_sha == @start_sha }
unless @start_version
- render_404
+ @start_sha = @merge_request_diff.head_commit_sha
+ @start_version = @merge_request_diff
end
end
@@ -346,6 +347,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def merge_widget_refresh
+ if merge_request.in_progress_merge_commit_sha || merge_request.state == 'merged'
+ @status = :success
+ elsif merge_request.merge_when_build_succeeds
+ @status = :merge_when_build_succeeds
+ end
+
+ render 'merge'
+ end
+
def branch_from
# This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
@@ -408,10 +419,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
else
ci_service = @merge_request.source_project.try(:ci_service)
status = ci_service.commit_status(merge_request.diff_head_sha, merge_request.source_branch) if ci_service
-
- if ci_service.respond_to?(:commit_coverage)
- coverage = ci_service.commit_coverage(merge_request.diff_head_sha, merge_request.source_branch)
- end
end
response = {
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index b71509f2c9b..c5d93ce25bc 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -23,7 +23,8 @@ class Projects::NotesController < Projects::ApplicationController
end
def create
- @note = Notes::CreateService.new(project, current_user, note_params).execute
+ create_params = note_params.merge(merge_request_diff_head_sha: params[:merge_request_diff_head_sha])
+ @note = Notes::CreateService.new(project, current_user, create_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index cc347922c6a..84451257b98 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -7,11 +7,33 @@ class Projects::PipelinesController < Projects::ApplicationController
def index
@scope = params[:scope]
- @pipelines = PipelinesFinder.new(project).execute(scope: @scope).page(params[:page]).per(30)
- @pipelines = @pipelines.includes(project: :namespace)
+ @pipelines = PipelinesFinder
+ .new(project)
+ .execute(scope: @scope)
+ .page(params[:page])
+ .per(30)
- @running_or_pending_count = PipelinesFinder.new(project).execute(scope: 'running').count
- @pipelines_count = PipelinesFinder.new(project).execute.count
+ @running_or_pending_count = PipelinesFinder
+ .new(project).execute(scope: 'running').count
+
+ @pipelines_count = PipelinesFinder
+ .new(project).execute.count
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render json: {
+ pipelines: PipelineSerializer
+ .new(project: @project, user: @current_user)
+ .with_pagination(request, response)
+ .represent(@pipelines),
+ count: {
+ all: @pipelines_count,
+ running_or_pending: @running_or_pending_count
+ }
+ }
+ end
+ end
end
def new
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index 3aec6f18e27..6e158e685e9 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -6,54 +6,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
def index
- @sort = params[:sort].presence || sort_value_name
- @group_links = @project.project_group_links
-
- @project_members = @project.project_members
- @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
-
- group = @project.group
-
- if group
- # We need `.where.not(user_id: nil)` here otherwise when a group has an
- # invitee, it would make the following query return 0 rows since a NULL
- # user_id would be present in the subquery
- # See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values
- # FIXME: This whole logic should be moved to a finder!
- non_null_user_ids = @project_members.where.not(user_id: nil).select(:user_id)
- group_members = group.group_members.where.not(user_id: non_null_user_ids)
- group_members = group_members.non_invite unless can?(current_user, :admin_group, @group)
- end
-
- if params[:search].present?
- user_ids = @project.users.search(params[:search]).select(:id)
- @project_members = @project_members.where(user_id: user_ids)
-
- if group_members
- user_ids = group.users.search(params[:search]).select(:id)
- group_members = group_members.where(user_id: user_ids)
- end
-
- @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
- end
-
- wheres = ["members.id IN (#{@project_members.select(:id).to_sql})"]
- wheres << "members.id IN (#{group_members.select(:id).to_sql})" if group_members
-
- @project_members = Member.
- where(wheres.join(' OR ')).
- sort(@sort).
- page(params[:page])
-
- @requesters = AccessRequestsFinder.new(@project).execute(current_user)
-
- @project_member = @project.project_members.new
+ sort = params[:sort].presence || sort_value_name
+ redirect_to namespace_project_settings_members_path(@project.namespace, @project, sort: sort)
end
def create
status = Members::CreateService.new(@project, current_user, params).execute
- redirect_url = namespace_project_project_members_path(@project.namespace, @project)
+ redirect_url = namespace_project_settings_members_path(@project.namespace, @project)
if status
redirect_to redirect_url, notice: 'Users were successfully added.'
@@ -76,14 +36,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
respond_to do |format|
format.html do
- redirect_to namespace_project_project_members_path(@project.namespace, @project)
+ redirect_to namespace_project_settings_members_path(@project.namespace, @project)
end
format.js { head :ok }
end
end
def resend_invite
- redirect_path = namespace_project_project_members_path(@project.namespace, @project)
+ redirect_path = namespace_project_settings_members_path(@project.namespace, @project)
@project_member = @project.project_members.find(params[:id])
@@ -106,7 +66,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
return render_404
end
- redirect_to(namespace_project_project_members_path(project.namespace, project),
+ redirect_to(namespace_project_settings_members_path(project.namespace, project),
notice: notice)
end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 3602b3d5e58..667f4870c7a 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -32,12 +32,6 @@ class Projects::RefsController < Projects::ApplicationController
redirect_to new_path
end
- format.js do
- @ref = params[:ref]
- define_tree_vars
- tree
- render "tree"
- end
end
end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 30c2a5d9982..17cb1d5be24 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -9,10 +9,6 @@ class Projects::ServicesController < Projects::ApplicationController
layout "project_settings"
- def index
- @services = @project.find_or_initialize_services
- end
-
def edit
end
diff --git a/app/controllers/projects/settings/integrations_controller.rb b/app/controllers/projects/settings/integrations_controller.rb
new file mode 100644
index 00000000000..fb2a4837735
--- /dev/null
+++ b/app/controllers/projects/settings/integrations_controller.rb
@@ -0,0 +1,18 @@
+module Projects
+ module Settings
+ class IntegrationsController < Projects::ApplicationController
+ include ServiceParams
+
+ before_action :authorize_admin_project!
+ layout "project_settings"
+
+ def show
+ @hooks = @project.hooks
+ @hook = ProjectHook.new
+
+ # Services
+ @services = @project.find_or_initialize_services
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/settings/members_controller.rb b/app/controllers/projects/settings/members_controller.rb
new file mode 100644
index 00000000000..5735e281f66
--- /dev/null
+++ b/app/controllers/projects/settings/members_controller.rb
@@ -0,0 +1,55 @@
+module Projects
+ module Settings
+ class MembersController < Projects::ApplicationController
+ include SortingHelper
+
+ def show
+ @sort = params[:sort].presence || sort_value_name
+ @group_links = @project.project_group_links
+
+ @project_members = @project.project_members
+ @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
+
+ group = @project.group
+
+ # group links
+ @group_links = @project.project_group_links.all
+
+ @skip_groups = @group_links.pluck(:group_id)
+ @skip_groups << @project.namespace_id unless @project.personal?
+
+ if group
+ # We need `.where.not(user_id: nil)` here otherwise when a group has an
+ # invitee, it would make the following query return 0 rows since a NULL
+ # user_id would be present in the subquery
+ # See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values
+ group_members = MembersFinder.new(@project_members, group).execute(current_user)
+ end
+
+ if params[:search].present?
+ user_ids = @project.users.search(params[:search]).select(:id)
+ @project_members = @project_members.where(user_id: user_ids)
+
+ if group_members
+ user_ids = group.users.search(params[:search]).select(:id)
+ group_members = group_members.where(user_id: user_ids)
+ end
+
+ @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id))
+ end
+
+ wheres = ["members.id IN (#{@project_members.select(:id).to_sql})"]
+ wheres << "members.id IN (#{group_members.select(:id).to_sql})" if group_members
+
+ @project_members = Member.
+ where(wheres.join(' OR ')).
+ sort(@sort).
+ page(params[:page])
+
+ @requesters = AccessRequestsFinder.new(@project).execute(current_user)
+
+ @project_member = @project.project_members.new
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index d5ee503c44c..444ff837bb3 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -42,19 +42,16 @@ class ProjectsController < Projects::ApplicationController
end
def update
- status = ::Projects::UpdateService.new(@project, current_user, project_params).execute
+ result = ::Projects::UpdateService.new(@project, current_user, project_params).execute
# Refresh the repo in case anything changed
- @repository = project.repository
+ @repository = @project.repository
respond_to do |format|
- if status
+ if result[:status] == :success
flash[:notice] = "Project '#{@project.name}' was successfully updated."
format.html do
- redirect_to(
- edit_project_path(@project),
- notice: "Project '#{@project.name}' was successfully updated."
- )
+ redirect_to(edit_project_path(@project))
end
else
format.html { render 'edit' }
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index c45196cc3e9..bf27f3d4d51 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -7,17 +7,17 @@ class RegistrationsController < Devise::RegistrationsController
end
def create
- if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
- # To avoid duplicate form fields on the login page, the registration form
- # names fields using `new_user`, but Devise still wants the params in
- # `user`.
- if params["new_#{resource_name}"].present? && params[resource_name].blank?
- params[resource_name] = params.delete(:"new_#{resource_name}")
- end
+ # To avoid duplicate form fields on the login page, the registration form
+ # names fields using `new_user`, but Devise still wants the params in
+ # `user`.
+ if params["new_#{resource_name}"].present? && params[resource_name].blank?
+ params[resource_name] = params.delete(:"new_#{resource_name}")
+ end
+ if !Gitlab::Recaptcha.load_configurations! || verify_recaptcha
super
else
- flash[:alert] = "There was an error with the reCAPTCHA code below. Please re-enter the code."
+ flash[:alert] = 'There was an error with the reCAPTCHA. Please re-solve the reCAPTCHA.'
flash.delete :recaptcha_error
render action: 'new'
end
@@ -57,7 +57,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def sign_up_params
- params.require(:user).permit(:username, :email, :name, :password, :password_confirmation)
+ params.require(:user).permit(:username, :email, :email_confirmation, :name, :password)
end
def resource_name
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index b666aa01d6b..6576ebd5235 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -45,6 +45,8 @@ class SearchController < ApplicationController
end
@search_objects = @search_results.objects(@scope, params[:page])
+
+ check_single_commit_result
end
def autocomplete
@@ -59,4 +61,16 @@ class SearchController < ApplicationController
render json: search_autocomplete_opts(term).to_json
end
+
+ private
+
+ def check_single_commit_result
+ if @search_results.single_commit_result?
+ only_commit = @search_results.objects('commits').first
+ query = params[:search].strip.downcase
+ found_by_commit_sha = Commit.valid_hash?(query) && only_commit.sha.start_with?(query)
+
+ redirect_to namespace_project_commit_path(@project.namespace, @project, only_commit) if found_by_commit_sha
+ end
+ end
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index b4c14d05eaf..1576fc80a6b 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -165,31 +165,53 @@ class IssuableFinder
end
end
- def assignee?
- params[:assignee_id].present?
+ def assignee_id?
+ params[:assignee_id].present? && params[:assignee_id] != NONE
+ end
+
+ def assignee_username?
+ params[:assignee_username].present? && params[:assignee_username] != NONE
+ end
+
+ def no_assignee?
+ # Assignee_id takes precedence over assignee_username
+ params[:assignee_id] == NONE || params[:assignee_username] == NONE
end
def assignee
return @assignee if defined?(@assignee)
@assignee =
- if assignee? && params[:assignee_id] != NONE
- User.find(params[:assignee_id])
+ if assignee_id?
+ User.find_by(id: params[:assignee_id])
+ elsif assignee_username?
+ User.find_by(username: params[:assignee_username])
else
nil
end
end
- def author?
- params[:author_id].present?
+ def author_id?
+ params[:author_id].present? && params[:author_id] != NONE
+ end
+
+ def author_username?
+ params[:author_username].present? && params[:author_username] != NONE
+ end
+
+ def no_author?
+ # author_id takes precedence over author_username
+ params[:author_id] == NONE || params[:author_username] == NONE
end
def author
return @author if defined?(@author)
@author =
- if author? && params[:author_id] != NONE
- User.find(params[:author_id])
+ if author_id?
+ User.find_by(id: params[:author_id])
+ elsif author_username?
+ User.find_by(username: params[:author_username])
else
nil
end
@@ -263,16 +285,24 @@ class IssuableFinder
end
def by_assignee(items)
- if assignee?
- items = items.where(assignee_id: assignee.try(:id))
+ if assignee
+ items = items.where(assignee_id: assignee.id)
+ elsif no_assignee?
+ items = items.where(assignee_id: nil)
+ elsif assignee_id? || assignee_username? # assignee not found
+ items = items.none
end
items
end
def by_author(items)
- if author?
- items = items.where(author_id: author.try(:id))
+ if author
+ items = items.where(author_id: author.id)
+ elsif no_author?
+ items = items.where(author_id: nil)
+ elsif author_id? || author_username? # author not found
+ items = items.none
end
items
diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb
new file mode 100644
index 00000000000..702944404f5
--- /dev/null
+++ b/app/finders/members_finder.rb
@@ -0,0 +1,13 @@
+class MembersFinder < Projects::ApplicationController
+ def initialize(project_members, project_group)
+ @project_members = project_members
+ @project_group = project_group
+ end
+
+ def execute(current_user)
+ non_null_user_ids = @project_members.where.not(user_id: nil).select(:user_id)
+ group_members = @project_group.group_members.where.not(user_id: non_null_user_ids)
+ group_members = group_members.non_invite unless can?(current_user, :admin_group, @project_group)
+ group_members
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index c816b616631..a112928c6de 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -244,7 +244,9 @@ module ApplicationHelper
scope: params[:scope],
milestone_title: params[:milestone_title],
assignee_id: params[:assignee_id],
+ assignee_username: params[:assignee_username],
author_id: params[:author_id],
+ author_username: params[:author_username],
search: params[:search],
label_name: params[:label_name]
}
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index aa54ee07bdc..2aa0449c46e 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -3,7 +3,7 @@ module CompareHelper
from.present? &&
to.present? &&
from != to &&
- project.feature_available?(:merge_requests, current_user) &&
+ can?(current_user, :create_merge_request, project) &&
project.repository.branch_names.include?(from) &&
project.repository.branch_names.include?(to)
end
diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb
index c35d6611ab0..aed1d7c839f 100644
--- a/app/helpers/diff_helper.rb
+++ b/app/helpers/diff_helper.rb
@@ -165,4 +165,10 @@ module DiffHelper
link_to "#{hide_whitespace? ? 'Show' : 'Hide'} whitespace changes", url, class: options[:class]
end
+
+ def render_overflow_warning?(diff_files)
+ diffs = @merge_request_diff.presence || diff_files
+
+ diffs.overflow?
+ end
end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index eb435cc1783..6d365ea9251 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -110,6 +110,28 @@ module GitlabMarkdownHelper
end
end
+ # Returns the text necessary to reference `entity` across projects
+ #
+ # project - Project to reference
+ # entity - Object that responds to `to_reference`
+ #
+ # Examples:
+ #
+ # cross_project_reference(project, project.issues.first)
+ # # => 'namespace1/project1#123'
+ #
+ # cross_project_reference(project, project.merge_requests.first)
+ # # => 'namespace1/project1!345'
+ #
+ # Returns a String
+ def cross_project_reference(project, entity)
+ if entity.respond_to?(:to_reference)
+ entity.to_reference(project, full: true)
+ else
+ ''
+ end
+ end
+
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
@@ -158,28 +180,6 @@ module GitlabMarkdownHelper
end
end
- # Returns the text necessary to reference `entity` across projects
- #
- # project - Project to reference
- # entity - Object that responds to `to_reference`
- #
- # Examples:
- #
- # cross_project_reference(project, project.issues.first)
- # # => 'namespace1/project1#123'
- #
- # cross_project_reference(project, project.merge_requests.first)
- # # => 'namespace1/project1!345'
- #
- # Returns a String
- def cross_project_reference(project, entity)
- if entity.respond_to?(:to_reference)
- entity.to_reference(project)
- else
- ''
- end
- end
-
def markdown_toolbar_button(options = {})
data = options[:data].merge({ container: "body" })
content_tag :button,
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 99db73c9ee0..2159e4ce21a 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -206,4 +206,13 @@ module GitlabRoutingHelper
file_namespace_project_build_artifacts_path(*args)
end
end
+
+ # Settings
+ def project_settings_integrations_path(project, *args)
+ namespace_project_settings_integrations_path(project.namespace, project, *args)
+ end
+
+ def project_settings_members_path(project, *args)
+ namespace_project_settings_members_path(project.namespace, project, *args)
+ end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 77dc9e7d538..926c9703628 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -14,7 +14,7 @@ module GroupsHelper
def group_title(group, name = nil, url = nil)
full_title = ''
- group.parents.each do |parent|
+ group.ancestors.each do |parent|
full_title += link_to(simple_sanitize(parent.name), group_path(parent))
full_title += ' / '.html_safe
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 1c213983a5b..e5bb8b93e76 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -30,6 +30,15 @@ module IssuablesHelper
end
end
+ def serialize_issuable(issuable)
+ case issuable
+ when Issue
+ IssueSerializer.new.represent(issuable).to_json
+ when MergeRequest
+ MergeRequestSerializer.new.represent(issuable).to_json
+ end
+ end
+
def template_dropdown_tag(issuable, &block)
title = selected_template(issuable) || "Choose a template"
options = {
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index a8a49e43b05..a2d21b67a77 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -58,13 +58,13 @@ module IssuesHelper
end
def status_box_class(item)
- if item.respond_to?(:expired?) && item.expired?
+ if item.try(:expired?)
'status-box-expired'
- elsif item.respond_to?(:merged?) && item.merged?
+ elsif item.try(:merged?)
'status-box-merged'
elsif item.closed?
'status-box-closed'
- elsif item.respond_to?(:upcoming?) && item.upcoming?
+ elsif item.try(:upcoming?)
'status-box-upcoming'
else
'status-box-open'
@@ -128,8 +128,10 @@ module IssuesHelper
names.to_sentence
end
- def award_active_class(awards, current_user)
- if current_user && awards.find { |a| a.user_id == current_user.id }
+ def award_state_class(awards, current_user)
+ if !current_user
+ "disabled"
+ elsif current_user && awards.find { |a| a.user_id == current_user.id }
"active"
else
""
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 20218775659..8c2c4e8833b 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -19,6 +19,14 @@ module MergeRequestsHelper
}
end
+ def mr_widget_refresh_url(mr)
+ if mr && mr.source_project
+ merge_widget_refresh_namespace_project_merge_request_url(mr.source_project.namespace, mr.source_project, mr)
+ else
+ ''
+ end
+ end
+
def mr_css_classes(mr)
classes = "merge-request"
classes << " closed" if mr.closed?
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index c6c63918fa5..eb98204285d 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -409,4 +409,15 @@ module ProjectsHelper
def project_issues(project)
IssuesFinder.new(current_user, project_id: project.id).execute
end
+
+ def visibility_select_options(project, selected_level)
+ levels_options_array = Gitlab::VisibilityLevel.values.map do |level|
+ [
+ visibility_level_label(level),
+ { data: { description: visibility_level_description(level, project) } },
+ level
+ ]
+ end
+ options_for_select(levels_options_array, selected_level)
+ end
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index cdb9663877c..6654f6997ce 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -39,7 +39,7 @@ module SearchHelper
# Autocomplete results for various settings pages
def default_autocomplete
[
- { category: "Settings", label: "Profile settings", url: profile_path },
+ { category: "Settings", label: "User settings", url: profile_path },
{ category: "Settings", label: "SSH Keys", url: profile_keys_path },
{ category: "Settings", label: "Dashboard", url: root_path },
{ category: "Settings", label: "Admin Section", url: admin_root_path },
@@ -75,7 +75,7 @@ module SearchHelper
{ category: "Current Project", label: "Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
{ category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
{ category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
- { category: "Current Project", label: "Members", url: namespace_project_project_members_path(@project.namespace, @project) },
+ { category: "Current Project", label: "Members", url: namespace_project_settings_members_path(@project.namespace, @project) },
{ category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
]
else
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index 9bab140e60a..715e5893a2c 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -1,23 +1,23 @@
module ServicesHelper
def service_event_description(event)
case event
- when "push"
+ when "push", "push_events"
"Event will be triggered by a push to the repository"
- when "tag_push"
+ when "tag_push", "tag_push_events"
"Event will be triggered when a new tag is pushed to the repository"
- when "note"
+ when "note", "note_events"
"Event will be triggered when someone adds a comment"
- when "issue"
+ when "issue", "issue_events"
"Event will be triggered when an issue is created/updated/closed"
- when "confidential_issue"
+ when "confidential_issue", "confidential_issue_events"
"Event will be triggered when a confidential issue is created/updated/closed"
- when "merge_request"
+ when "merge_request", "merge_request_events"
"Event will be triggered when a merge request is created/updated/merged"
- when "build"
+ when "build", "build_events"
"Event will be triggered when a build status changes"
- when "wiki_page"
+ when "wiki_page", "wiki_page_events"
"Event will be triggered when a wiki page is created/updated"
- when "commit"
+ when "commit", "commit_events"
"Event will be triggered when a commit is created/updated"
end
end
@@ -26,4 +26,6 @@ module ServicesHelper
event = event.pluralize if %w[merge_request issue confidential_issue].include?(event)
"#{event}_events"
end
+
+ extend self
end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 74de25acf9d..c568cca9e5e 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -11,9 +11,10 @@ module TodosHelper
case todo.action
when Todo::ASSIGNED then 'assigned you'
when Todo::MENTIONED then 'mentioned you on'
- when Todo::BUILD_FAILED then 'The build failed for your'
+ when Todo::BUILD_FAILED then 'The build failed for'
when Todo::MARKED then 'added a todo for'
when Todo::APPROVAL_REQUIRED then 'set you as an approver for'
+ when Todo::UNMERGEABLE then 'Could not merge'
end
end
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index a674564c4ec..456598b4c28 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -1,7 +1,8 @@
module VersionCheckHelper
def version_status_badge
if Rails.env.production? && current_application_settings.version_check_enabled
- image_tag VersionCheck.new.url
+ image_url = VersionCheck.new.url
+ image_tag image_url, class: 'js-version-status-badge'
end
end
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 0d20c9092c4..46fa6fd9f6d 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -38,6 +38,14 @@ module Emails
mail_answer_thread(@snippet, note_thread_options(recipient_id))
end
+ def note_personal_snippet_email(recipient_id, note_id)
+ setup_note_mail(note_id, recipient_id)
+
+ @snippet = @note.noteable
+ @target_url = snippet_url(@note.noteable)
+ mail_answer_thread(@snippet, note_thread_options(recipient_id))
+ end
+
private
def note_target_url_options
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 0bc1c19e9cd..0cd3456b4de 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -107,15 +107,11 @@ class Notify < BaseMailer
def mail_thread(model, headers = {})
add_project_headers
+ add_unsubscription_headers_and_links
+
headers["X-GitLab-#{model.class.name}-ID"] = model.id
headers['X-GitLab-Reply-Key'] = reply_key
- if !@labels_url && @sent_notification && @sent_notification.unsubscribable?
- headers['List-Unsubscribe'] = "<#{unsubscribe_sent_notification_url(@sent_notification, force: true)}>"
-
- @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
- end
-
if Gitlab::IncomingEmail.enabled?
address = Mail::Address.new(Gitlab::IncomingEmail.reply_address(reply_key))
address.display_name = @project.name_with_namespace
@@ -171,4 +167,16 @@ class Notify < BaseMailer
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end
+
+ def add_unsubscription_headers_and_links
+ return unless !@labels_url && @sent_notification && @sent_notification.unsubscribable?
+
+ list_unsubscribe_methods = [unsubscribe_sent_notification_url(@sent_notification, force: true)]
+ if Gitlab::IncomingEmail.enabled? && Gitlab::IncomingEmail.supports_wildcard?
+ list_unsubscribe_methods << "mailto:#{Gitlab::IncomingEmail.unsubscribe_address(reply_key)}"
+ end
+
+ headers['List-Unsubscribe'] = list_unsubscribe_methods.map { |e| "<#{e}>" }.join(',')
+ @sent_notification_url = unsubscribe_sent_notification_url(@sent_notification)
+ end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index fa8f8bc3a5f..ad6c588202e 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -22,6 +22,17 @@ class Ability
end
end
+ # Given a list of users and a snippet this method returns the users that can
+ # read the given snippet.
+ def users_that_can_read_personal_snippet(users, snippet)
+ case snippet.visibility_level
+ when Snippet::INTERNAL, Snippet::PUBLIC
+ users
+ when Snippet::PRIVATE
+ users.include?(snippet.author) ? [snippet.author] : []
+ end
+ end
+
# Returns an Array of Issues that can be read by the given user.
#
# issues - The issues to reduce down to those readable by the user.
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index bf463a3b6bb..2df8b071e13 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -68,6 +68,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :koding_enabled
+ validates :plantuml_url,
+ presence: true,
+ if: :plantuml_enabled
+
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
@@ -152,51 +156,64 @@ class ApplicationSetting < ActiveRecord::Base
def self.expire
Rails.cache.delete(CACHE_KEY)
+ rescue
+ # Gracefully handle when Redis is not available. For example,
+ # omnibus may fail here during gitlab:assets:compile.
end
def self.cached
Rails.cache.fetch(CACHE_KEY)
end
- def self.create_from_defaults
- create(
- default_projects_limit: Settings.gitlab['default_projects_limit'],
- default_branch_protection: Settings.gitlab['default_branch_protection'],
- signup_enabled: Settings.gitlab['signup_enabled'],
- signin_enabled: Settings.gitlab['signin_enabled'],
- gravatar_enabled: Settings.gravatar['enabled'],
- sign_in_text: nil,
+ def self.defaults_ce
+ {
after_sign_up_text: nil,
- help_page_text: nil,
- shared_runners_text: nil,
- restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
- max_attachment_size: Settings.gitlab['max_attachment_size'],
- session_expire_delay: Settings.gitlab['session_expire_delay'],
+ akismet_enabled: false,
+ container_registry_token_expire_delay: 5,
+ default_branch_protection: Settings.gitlab['default_branch_protection'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ disabled_oauth_sign_in_sources: [],
domain_whitelist: Settings.gitlab['domain_whitelist'],
+ gravatar_enabled: Settings.gravatar['enabled'],
+ help_page_text: nil,
+ housekeeping_bitmaps_enabled: true,
+ housekeeping_enabled: true,
+ housekeeping_full_repack_period: 50,
+ housekeeping_gc_period: 200,
+ housekeeping_incremental_repack_period: 10,
import_sources: Gitlab::ImportSources.values,
- shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
- max_artifacts_size: Settings.artifacts['max_size'],
- require_two_factor_authentication: false,
- two_factor_grace_period: 48,
- recaptcha_enabled: false,
- akismet_enabled: false,
koding_enabled: false,
koding_url: nil,
+ max_artifacts_size: Settings.artifacts['max_size'],
+ max_attachment_size: Settings.gitlab['max_attachment_size'],
+ plantuml_enabled: false,
+ plantuml_url: nil,
+ recaptcha_enabled: false,
repository_checks_enabled: true,
- disabled_oauth_sign_in_sources: [],
- send_user_confirmation_email: false,
- container_registry_token_expire_delay: 5,
repository_storages: ['default'],
- user_default_external: false,
+ require_two_factor_authentication: false,
+ restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
+ session_expire_delay: Settings.gitlab['session_expire_delay'],
+ send_user_confirmation_email: false,
+ shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
+ shared_runners_text: nil,
sidekiq_throttling_enabled: false,
- housekeeping_enabled: true,
- housekeeping_bitmaps_enabled: true,
- housekeeping_incremental_repack_period: 10,
- housekeeping_full_repack_period: 50,
- housekeeping_gc_period: 200,
- )
+ sign_in_text: nil,
+ signin_enabled: Settings.gitlab['signin_enabled'],
+ signup_enabled: Settings.gitlab['signup_enabled'],
+ two_factor_grace_period: 48,
+ user_default_external: false
+ }
+ end
+
+ def self.defaults
+ defaults_ce
+ end
+
+ def self.create_from_defaults
+ create(defaults)
end
def home_page_url_column_exist
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 27042798741..5fe8ddf69d7 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -2,6 +2,7 @@ module Ci
class Build < CommitStatus
include TokenAuthenticatable
include AfterCommitQueue
+ include Presentable
belongs_to :runner
belongs_to :trigger_request
@@ -91,6 +92,12 @@ module Ci
end
state_machine :status do
+ after_transition any => [:pending] do |build|
+ build.run_after_commit do
+ BuildQueueWorker.perform_async(id)
+ end
+ end
+
after_transition pending: :running do |build|
build.run_after_commit do
BuildHooksWorker.perform_async(id)
@@ -507,6 +514,10 @@ module Ci
end
end
+ def has_expiring_artifacts?
+ artifacts_expire_at.present?
+ end
+
def keep_artifacts!
self.update(artifacts_expire_at: nil)
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index abbbddaa4f6..fab8497ec7d 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -128,21 +128,26 @@ module Ci
end
def stages
+ # TODO, this needs refactoring, see gitlab-ce#26481.
+
+ stages_query = statuses
+ .group('stage').select(:stage).order('max(stage_idx)')
+
status_sql = statuses.latest.where('stage=sg.stage').status_sql
- stages_query = statuses.group('stage').select(:stage)
- .order('max(stage_idx)')
+ warnings_sql = statuses.latest.select('COUNT(*) > 0')
+ .where('stage=sg.stage').failed_but_allowed.to_sql
- stages_with_statuses = CommitStatus.from(stages_query, :sg).
- pluck('sg.stage', status_sql)
+ stages_with_statuses = CommitStatus.from(stages_query, :sg)
+ .pluck('sg.stage', status_sql, "(#{warnings_sql})")
stages_with_statuses.map do |stage|
- Ci::Stage.new(self, name: stage.first, status: stage.last)
+ Ci::Stage.new(self, Hash[%i[name status warnings].zip(stage)])
end
end
def artifacts
- builds.latest.with_artifacts_not_expired
+ builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
def project_id
@@ -191,7 +196,11 @@ module Ci
end
def manual_actions
- builds.latest.manual_actions
+ builds.latest.manual_actions.includes(project: [:namespace])
+ end
+
+ def stuck?
+ builds.pending.any?(&:stuck?)
end
def retryable?
@@ -283,6 +292,10 @@ module Ci
end
end
+ def has_yaml_errors?
+ yaml_errors.present?
+ end
+
def environments
builds.where.not(environment: nil).success.pluck(:environment).uniq
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 123930273e0..ed1843ba005 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -2,6 +2,7 @@ module Ci
class Runner < ActiveRecord::Base
extend Ci::Model
+ RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
LAST_CONTACT_TIME = 1.hour.ago
AVAILABLE_SCOPES = %w[specific shared active paused online]
FORM_EDITABLE = %i[description tag_list active run_untagged locked]
@@ -21,6 +22,8 @@ module Ci
scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) }
scope :ordered, ->() { order(id: :desc) }
+ after_save :tick_runner_queue, if: :form_editable_changed?
+
scope :owned_or_shared, ->(project_id) do
joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id')
.where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id)
@@ -122,8 +125,38 @@ module Ci
]
end
+ def tick_runner_queue
+ SecureRandom.hex.tap do |new_update|
+ Gitlab::Redis.with do |redis|
+ redis.set(runner_queue_key, new_update, ex: RUNNER_QUEUE_EXPIRY_TIME)
+ end
+ end
+ end
+
+ def ensure_runner_queue_value
+ Gitlab::Redis.with do |redis|
+ value = SecureRandom.hex
+ redis.set(runner_queue_key, value, ex: RUNNER_QUEUE_EXPIRY_TIME, nx: true)
+ redis.get(runner_queue_key)
+ end
+ end
+
+ def is_runner_queue_value_latest?(value)
+ ensure_runner_queue_value == value if value.present?
+ end
+
private
+ def runner_queue_key
+ "runner:build_queue:#{self.token}"
+ end
+
+ def form_editable_changed?
+ FORM_EDITABLE.any? do |editable|
+ public_send("#{editable}_changed?")
+ end
+ end
+
def tag_constraints
unless has_tags? || run_untagged?
errors.add(:tags_list,
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index d035eda6df5..ca74c91b062 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -8,10 +8,11 @@ module Ci
delegate :project, to: :pipeline
- def initialize(pipeline, name:, status: nil)
+ def initialize(pipeline, name:, status: nil, warnings: nil)
@pipeline = pipeline
@name = name
@status = status
+ @warnings = warnings
end
def to_param
@@ -39,5 +40,17 @@ module Ci
def builds
@builds ||= pipeline.builds.where(stage: name)
end
+
+ def success?
+ status.to_s == 'success'
+ end
+
+ def has_warnings?
+ if @warnings.nil?
+ statuses.latest.failed_but_allowed.any?
+ else
+ @warnings
+ end
+ end
end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 69cfc47f5bf..316bd2e512b 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -21,6 +21,9 @@ class Commit
DIFF_HARD_LIMIT_FILES = 1000
DIFF_HARD_LIMIT_LINES = 50000
+ # The SHA can be between 7 and 40 hex characters.
+ COMMIT_SHA_PATTERN = '\h{7,40}'
+
class << self
def decorate(commits, project)
commits.map do |commit|
@@ -52,6 +55,10 @@ class Commit
def from_hash(hash, project)
new(Gitlab::Git::Commit.new(hash), project)
end
+
+ def valid_hash?(key)
+ !!(/\A#{COMMIT_SHA_PATTERN}\z/ =~ key)
+ end
end
attr_accessor :raw
@@ -77,8 +84,6 @@ class Commit
# Pattern used to extract commit references from text
#
- # The SHA can be between 7 and 40 hex characters.
- #
# This pattern supports cross-project references.
def self.reference_pattern
@reference_pattern ||= %r{
@@ -88,11 +93,11 @@ class Commit
end
def self.link_reference_pattern
- @link_reference_pattern ||= super("commit", /(?<commit>\h{7,40})/)
+ @link_reference_pattern ||= super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})/)
end
- def to_reference(from_project = nil)
- commit_reference(from_project, id)
+ def to_reference(from_project = nil, full: false)
+ commit_reference(from_project, id, full: full)
end
def reference_link_text(from_project = nil)
@@ -318,10 +323,24 @@ class Commit
Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options)
end
+ def persisted?
+ true
+ end
+
+ def touch
+ # no-op but needs to be defined since #persisted? is defined
+ end
+
+ WIP_REGEX = /\A\s*(((?i)(\[WIP\]|WIP:|WIP)\s|WIP$))|(fixup!|squash!)\s/.freeze
+
+ def work_in_progress?
+ !!(title =~ WIP_REGEX)
+ end
+
private
- def commit_reference(from_project, referable_commit_id)
- reference = project.to_reference(from_project)
+ def commit_reference(from_project, referable_commit_id, full: false)
+ reference = project.to_reference(from_project, full: full)
if reference.present?
"#{reference}#{self.class.reference_prefix}#{referable_commit_id}"
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index d9af7f6c139..84e2e8a5dd5 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -89,8 +89,8 @@ class CommitRange
alias_method :id, :to_s
- def to_reference(from_project = nil)
- project_reference = project.to_reference(from_project)
+ def to_reference(from_project = nil, full: false)
+ project_reference = project.to_reference(from_project, full: full)
if project_reference.present?
project_reference + self.class.reference_prefix + self.id
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 31cd381dcd2..9547c57b2ae 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -137,4 +137,10 @@ class CommitStatus < ActiveRecord::Base
.new(self, current_user)
.fabricate!
end
+
+ def sortable_name
+ name.split(/(\d+)/).map do |v|
+ v =~ /\d+/ ? v.to_i : v
+ end
+ end
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 90bd6490a02..a600f9c14c5 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -51,6 +51,10 @@ module CacheMarkdownField
CACHING_CLASSES.map(&:constantize)
end
+ def skip_project_check?
+ false
+ end
+
extend ActiveSupport::Concern
included do
@@ -112,7 +116,8 @@ module CacheMarkdownField
invalidation_method = "#{html_field}_invalidated?".to_sym
define_method(cache_method) do
- html = Banzai::Renderer.cacheless_render_field(self, markdown_field)
+ options = { skip_project_check: skip_project_check? }
+ html = Banzai::Renderer.cacheless_render_field(self, markdown_field, options)
__send__("#{html_field}=", html)
true
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 90432fc4050..431c0354969 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -1,6 +1,7 @@
module HasStatus
extend ActiveSupport::Concern
+ DEFAULT_STATUS = 'created'
AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped]
STARTED_STATUSES = %w[running success failed skipped]
ACTIVE_STATUSES = %w[pending running]
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 5e63825bf99..3517969eabc 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -13,6 +13,7 @@ module Issuable
include StripAttribute
include Awardable
include Taskable
+ include TimeTrackable
included do
cache_markdown_field :title, pipeline: :single_line
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 8ab0401d288..ef2c1e5d414 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -49,7 +49,11 @@ module Mentionable
self.class.mentionable_attrs.each do |attr, options|
text = __send__(attr)
- options = options.merge(cache_key: [self, attr], author: author)
+ options = options.merge(
+ cache_key: [self, attr],
+ author: author,
+ skip_project_check: skip_project_check?
+ )
extractor.analyze(text, options)
end
@@ -121,4 +125,8 @@ module Mentionable
def cross_reference_exists?(target)
SystemNoteService.cross_reference_exists?(target, local_reference)
end
+
+ def skip_project_check?
+ false
+ end
end
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 8f02c226f0b..e9450dd0c26 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -7,11 +7,14 @@ module Milestoneish
def total_items_count(user)
memoize_per_user(user, :total_items_count) do
- issues_count = count_issues_by_state(user).values.sum
- issues_count + merge_requests.size
+ total_issues_count(user) + merge_requests.size
end
end
+ def total_issues_count(user)
+ count_issues_by_state(user).values.sum
+ end
+
def complete?(user)
total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user)
end
@@ -36,8 +39,8 @@ module Milestoneish
def issues_visible_to_user(user)
memoize_per_user(user, :issues_visible_to_user) do
- params = try(:project_id) ? { project_id: project_id } : {}
- IssuesFinder.new(user, params).execute.where(milestone_id: milestoneish_ids)
+ IssuesFinder.new(user, issues_finder_params)
+ .execute.where(milestone_id: milestoneish_ids)
end
end
@@ -72,4 +75,10 @@ module Milestoneish
@memoized[method_name] ||= {}
@memoized[method_name][user.try!(:id)] ||= yield
end
+
+ # override in a class that includes this module to get a faster query
+ # from IssuesFinder
+ def issues_finder_params
+ {}
+ end
end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 70740c76e43..4865c0a14b1 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -96,6 +96,11 @@ module Participable
participants.merge(ext.users)
- Ability.users_that_can_read_project(participants.to_a, project)
+ case self
+ when PersonalSnippet
+ Ability.users_that_can_read_personal_snippet(participants.to_a, self)
+ else
+ Ability.users_that_can_read_project(participants.to_a, project)
+ end
end
end
diff --git a/app/models/concerns/presentable.rb b/app/models/concerns/presentable.rb
new file mode 100644
index 00000000000..7b33b837004
--- /dev/null
+++ b/app/models/concerns/presentable.rb
@@ -0,0 +1,7 @@
+module Presentable
+ def present(**attributes)
+ Gitlab::View::Presenter::Factory
+ .new(self, attributes)
+ .fabricate!
+ end
+end
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index 6d88951c713..60734bc6660 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -32,6 +32,6 @@ module ProjectFeaturesCompatibility
build_project_feature unless project_feature
access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
- project_feature.update_attribute(field, access_level)
+ project_feature.send(:write_attribute, field, access_level)
end
end
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index 944519a3070..2589215ad19 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -55,30 +55,30 @@ module ReactiveCaching
self.reactive_cache_refresh_interval = 1.minute
self.reactive_cache_lifetime = 10.minutes
- def calculate_reactive_cache
+ def calculate_reactive_cache(*args)
raise NotImplementedError
end
- def with_reactive_cache(&blk)
- within_reactive_cache_lifetime do
- data = Rails.cache.read(full_reactive_cache_key)
+ def with_reactive_cache(*args, &blk)
+ within_reactive_cache_lifetime(*args) do
+ data = Rails.cache.read(full_reactive_cache_key(*args))
yield data if data.present?
end
ensure
- Rails.cache.write(full_reactive_cache_key('alive'), true, expires_in: self.class.reactive_cache_lifetime)
- ReactiveCachingWorker.perform_async(self.class, id)
+ Rails.cache.write(alive_reactive_cache_key(*args), true, expires_in: self.class.reactive_cache_lifetime)
+ ReactiveCachingWorker.perform_async(self.class, id, *args)
end
- def clear_reactive_cache!
- Rails.cache.delete(full_reactive_cache_key)
+ def clear_reactive_cache!(*args)
+ Rails.cache.delete(full_reactive_cache_key(*args))
end
- def exclusively_update_reactive_cache!
- locking_reactive_cache do
- within_reactive_cache_lifetime do
- enqueuing_update do
- value = calculate_reactive_cache
- Rails.cache.write(full_reactive_cache_key, value)
+ def exclusively_update_reactive_cache!(*args)
+ locking_reactive_cache(*args) do
+ within_reactive_cache_lifetime(*args) do
+ enqueuing_update(*args) do
+ value = calculate_reactive_cache(*args)
+ Rails.cache.write(full_reactive_cache_key(*args), value)
end
end
end
@@ -93,22 +93,26 @@ module ReactiveCaching
([prefix].flatten + qualifiers).join(':')
end
- def locking_reactive_cache
- lease = Gitlab::ExclusiveLease.new(full_reactive_cache_key, timeout: reactive_cache_lease_timeout)
+ def alive_reactive_cache_key(*qualifiers)
+ full_reactive_cache_key(*(qualifiers + ['alive']))
+ end
+
+ def locking_reactive_cache(*args)
+ lease = Gitlab::ExclusiveLease.new(full_reactive_cache_key(*args), timeout: reactive_cache_lease_timeout)
uuid = lease.try_obtain
yield if uuid
ensure
- Gitlab::ExclusiveLease.cancel(full_reactive_cache_key, uuid)
+ Gitlab::ExclusiveLease.cancel(full_reactive_cache_key(*args), uuid)
end
- def within_reactive_cache_lifetime
- yield if Rails.cache.read(full_reactive_cache_key('alive'))
+ def within_reactive_cache_lifetime(*args)
+ yield if Rails.cache.read(alive_reactive_cache_key(*args))
end
- def enqueuing_update
+ def enqueuing_update(*args)
yield
ensure
- ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id)
+ ReactiveCachingWorker.perform_in(self.class.reactive_cache_refresh_interval, self.class, id, *args)
end
end
end
diff --git a/app/models/concerns/reactive_service.rb b/app/models/concerns/reactive_service.rb
new file mode 100644
index 00000000000..e1f868a299b
--- /dev/null
+++ b/app/models/concerns/reactive_service.rb
@@ -0,0 +1,10 @@
+module ReactiveService
+ extend ActiveSupport::Concern
+
+ included do
+ include ReactiveCaching
+
+ # Default cache key: class name + project_id
+ self.reactive_cache_key = ->(service) { [ service.class.model_name.singular, service.project_id ] }
+ end
+end
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
index 8ba009fe04f..da803c7f481 100644
--- a/app/models/concerns/referable.rb
+++ b/app/models/concerns/referable.rb
@@ -17,7 +17,7 @@ module Referable
# Issue.last.to_reference(other_project) # => "cross-project#1"
#
# Returns a String
- def to_reference(_from_project = nil)
+ def to_reference(_from_project = nil, full:)
''
end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 1108a64c59e..2b93aa30c0f 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -60,6 +60,21 @@ module Routable
joins(:route).where(wheres.join(' OR '))
end
end
+
+ # Builds a relation to find multiple objects that are nested under user membership
+ #
+ # Usage:
+ #
+ # Klass.member_descendants(1)
+ #
+ # Returns an ActiveRecord::Relation.
+ def member_descendants(user_id)
+ joins(:route).
+ joins("INNER JOIN routes r2 ON routes.path LIKE CONCAT(r2.path, '/%')
+ INNER JOIN members ON members.source_id = r2.source_id
+ AND members.source_type = r2.source_type").
+ where('members.user_id = ?', user_id)
+ end
end
private
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index ebc75100a54..25e2d8ea24e 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -11,10 +11,10 @@ module Taskable
INCOMPLETE = 'incomplete'.freeze
ITEM_PATTERN = /
^
- (?:\s*[-+*]|(?:\d+\.))? # optional list prefix
- \s* # optional whitespace prefix
- (\[\s\]|\[[xX]\]) # checkbox
- (\s.+) # followed by whitespace and some text.
+ \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list
+ \s+ # whitespace prefix has to be always presented for a list item
+ (\[\s\]|\[[xX]\]) # checkbox
+ (\s.+) # followed by whitespace and some text.
/x
def self.get_tasks(content)
diff --git a/app/models/concerns/time_trackable.rb b/app/models/concerns/time_trackable.rb
new file mode 100644
index 00000000000..040e3a2884e
--- /dev/null
+++ b/app/models/concerns/time_trackable.rb
@@ -0,0 +1,72 @@
+# == TimeTrackable concern
+#
+# Contains functionality related to objects that support time tracking.
+#
+# Used by Issue and MergeRequest.
+#
+
+module TimeTrackable
+ extend ActiveSupport::Concern
+
+ included do
+ attr_reader :time_spent, :time_spent_user
+
+ alias_method :time_spent?, :time_spent
+
+ default_value_for :time_estimate, value: 0, allows_nil: false
+
+ validates :time_estimate, numericality: { message: 'has an invalid format' }, allow_nil: false
+ validate :check_negative_time_spent
+
+ has_many :timelogs, as: :trackable, dependent: :destroy
+ end
+
+ def spend_time(options)
+ @time_spent = options[:duration]
+ @time_spent_user = options[:user]
+ @original_total_time_spent = nil
+
+ return if @time_spent == 0
+
+ if @time_spent == :reset
+ reset_spent_time
+ else
+ add_or_subtract_spent_time
+ end
+ end
+ alias_method :spend_time=, :spend_time
+
+ def total_time_spent
+ timelogs.sum(:time_spent)
+ end
+
+ def human_total_time_spent
+ Gitlab::TimeTrackingFormatter.output(total_time_spent)
+ end
+
+ def human_time_estimate
+ Gitlab::TimeTrackingFormatter.output(time_estimate)
+ end
+
+ private
+
+ def reset_spent_time
+ timelogs.new(time_spent: total_time_spent * -1, user: @time_spent_user)
+ end
+
+ def add_or_subtract_spent_time
+ timelogs.new(time_spent: time_spent, user: @time_spent_user)
+ end
+
+ def check_negative_time_spent
+ return if time_spent.nil? || time_spent == :reset
+
+ # we need to cache the total time spent so multiple calls to #valid?
+ # doesn't give a false error
+ @original_total_time_spent ||= total_time_spent
+
+ if time_spent < 0 && (time_spent.abs > @original_total_time_spent)
+ errors.add(:time_spent, 'Time to subtract exceeds the total time spent')
+ end
+ end
+end
diff --git a/app/models/concerns/valid_attribute.rb b/app/models/concerns/valid_attribute.rb
new file mode 100644
index 00000000000..8c35cea8d58
--- /dev/null
+++ b/app/models/concerns/valid_attribute.rb
@@ -0,0 +1,10 @@
+module ValidAttribute
+ extend ActiveSupport::Concern
+
+ # Checks whether an attribute has failed validation or not
+ #
+ # +attribute+ The symbolised name of the attribute i.e :name
+ def valid_attribute?(attribute)
+ self.errors.empty? || self.errors.messages[attribute].nil?
+ end
+end
diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
index ba4ee6fcf9d..d2e626c22e8 100644
--- a/app/models/cycle_analytics.rb
+++ b/app/models/cycle_analytics.rb
@@ -1,62 +1,38 @@
class CycleAnalytics
STAGES = %i[issue plan code test review staging production].freeze
- def initialize(project, current_user, from:)
+ def initialize(project, options)
@project = project
- @current_user = current_user
- @from = from
- @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil)
+ @options = options
end
def summary
- @summary ||= Summary.new(@project, @current_user, from: @from)
+ @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project,
+ from: @options[:from],
+ current_user: @options[:current_user]).data
end
- def permissions(user:)
- Gitlab::CycleAnalytics::Permissions.get(user: user, project: @project)
+ def stats
+ @stats ||= stats_per_stage
end
- def issue
- @fetcher.calculate_metric(:issue,
- Issue.arel_table[:created_at],
- [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
- Issue::Metrics.arel_table[:first_added_to_board_at]])
+ def no_stats?
+ stats.all? { |hash| hash[:value].nil? }
end
- def plan
- @fetcher.calculate_metric(:plan,
- [Issue::Metrics.arel_table[:first_associated_with_milestone_at],
- Issue::Metrics.arel_table[:first_added_to_board_at]],
- Issue::Metrics.arel_table[:first_mentioned_in_commit_at])
- end
-
- def code
- @fetcher.calculate_metric(:code,
- Issue::Metrics.arel_table[:first_mentioned_in_commit_at],
- MergeRequest.arel_table[:created_at])
- end
-
- def test
- @fetcher.calculate_metric(:test,
- MergeRequest::Metrics.arel_table[:latest_build_started_at],
- MergeRequest::Metrics.arel_table[:latest_build_finished_at])
+ def permissions(user:)
+ Gitlab::CycleAnalytics::Permissions.get(user: user, project: @project)
end
- def review
- @fetcher.calculate_metric(:review,
- MergeRequest.arel_table[:created_at],
- MergeRequest::Metrics.arel_table[:merged_at])
+ def [](stage_name)
+ Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options)
end
- def staging
- @fetcher.calculate_metric(:staging,
- MergeRequest::Metrics.arel_table[:merged_at],
- MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
- end
+ private
- def production
- @fetcher.calculate_metric(:production,
- Issue.arel_table[:created_at],
- MergeRequest::Metrics.arel_table[:first_deployed_to_production_at])
+ def stats_per_stage
+ STAGES.map do |stage_name|
+ self[stage_name].as_json
+ end
end
end
diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb
index 82f53d17ddd..e69de29bb2d 100644
--- a/app/models/cycle_analytics/summary.rb
+++ b/app/models/cycle_analytics/summary.rb
@@ -1,43 +0,0 @@
-class CycleAnalytics
- class Summary
- def initialize(project, current_user, from:)
- @project = project
- @current_user = current_user
- @from = from
- end
-
- def new_issues
- IssuesFinder.new(@current_user, project_id: @project.id).execute.created_after(@from).count
- end
-
- def commits
- ref = @project.default_branch.presence
- count_commits_for(ref)
- end
-
- def deploys
- @project.deployments.where("created_at > ?", @from).count
- end
-
- private
-
- # Don't use the `Gitlab::Git::Repository#log` method, because it enforces
- # a limit. Since we need a commit count, we _can't_ enforce a limit, so
- # the easiest way forward is to replicate the relevant portions of the
- # `log` function here.
- def count_commits_for(ref)
- return unless ref
-
- repository = @project.repository.raw_repository
- sha = @project.repository.commit(ref).sha
-
- cmd = %W(git --git-dir=#{repository.path} log)
- cmd << '--format=%H'
- cmd << "--after=#{@from.iso8601}"
- cmd << sha
-
- raw_output = IO.popen(cmd) { |io| io.read }
- raw_output.lines.count
- end
- end
-end
diff --git a/app/models/dashboard_milestone.rb b/app/models/dashboard_milestone.rb
new file mode 100644
index 00000000000..646c1e5ce1a
--- /dev/null
+++ b/app/models/dashboard_milestone.rb
@@ -0,0 +1,5 @@
+class DashboardMilestone < GlobalMilestone
+ def issues_finder_params
+ { authorized_only: true }
+ end
+end
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 5cde94b3509..652abf18a8a 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -87,7 +87,7 @@ class Environment < ActiveRecord::Base
end
def update_merge_request_metrics?
- self.name == "production"
+ (environment_type || name) == "production"
end
def first_deployment_for(commit)
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 91b508eb325..26712c19b5a 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -38,7 +38,7 @@ class ExternalIssue
@reference_pattern ||= %r{(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)}
end
- def to_reference(_from_project = nil)
+ def to_reference(_from_project = nil, full: nil)
id
end
diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb
index 9803bae0bee..36cf7ad6a28 100644
--- a/app/models/forked_project_link.rb
+++ b/app/models/forked_project_link.rb
@@ -1,4 +1,4 @@
class ForkedProjectLink < ActiveRecord::Base
- belongs_to :forked_to_project, class_name: Project
- belongs_to :forked_from_project, class_name: Project
+ belongs_to :forked_to_project, class_name: 'Project'
+ belongs_to :forked_from_project, class_name: 'Project'
end
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
index fa54e3540d0..8867ba0d2ff 100644
--- a/app/models/generic_commit_status.rb
+++ b/app/models/generic_commit_status.rb
@@ -1,6 +1,10 @@
class GenericCommitStatus < CommitStatus
before_validation :set_default_values
+ validates :target_url, addressable_url: true,
+ length: { maximum: 255 },
+ allow_nil: true
+
# GitHub compatible API
alias_attribute :context, :name
@@ -12,4 +16,10 @@ class GenericCommitStatus < CommitStatus
def tags
[:external]
end
+
+ def detailed_status(current_user)
+ Gitlab::Ci::Status::External::Factory
+ .new(self, current_user)
+ .fabricate!
+ end
end
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index a54e478f5f8..b991d78e27f 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -1,6 +1,8 @@
class GlobalMilestone
include Milestoneish
+ EPOCH = DateTime.parse('1970-01-01')
+
attr_accessor :title, :milestones
alias_attribute :name, :title
@@ -8,13 +10,22 @@ class GlobalMilestone
@first_milestone
end
- def self.build_collection(milestones)
- milestones = milestones.group_by(&:title)
+ def self.build_collection(projects, params)
+ child_milestones = MilestonesFinder.new.execute(projects, params)
- milestones.map do |title, milestones|
- milestones_relation = Milestone.where(id: milestones.map(&:id))
+ milestones = child_milestones.select(:id, :title).group_by(&:title).map do |title, grouped|
+ milestones_relation = Milestone.where(id: grouped.map(&:id))
new(title, milestones_relation)
end
+
+ milestones.sort_by { |milestone| milestone.due_date || EPOCH }
+ end
+
+ def self.build(projects, title)
+ child_milestones = Milestone.of_projects(projects).where(title: title)
+ return if child_milestones.blank?
+
+ new(title, child_milestones)
end
def initialize(title, milestones)
diff --git a/app/models/group.rb b/app/models/group.rb
index 9888b242e98..4cdfd022094 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -80,7 +80,7 @@ class Group < Namespace
end
end
- def to_reference(_from_project = nil)
+ def to_reference(_from_project = nil, full: nil)
"#{self.class.reference_prefix}#{name}"
end
@@ -201,7 +201,7 @@ class Group < Namespace
end
def members_with_parents
- GroupMember.where(requested_at: nil, source_id: parents.map(&:id).push(id))
+ GroupMember.where(requested_at: nil, source_id: ancestors.map(&:id).push(id))
end
def users_with_parents
diff --git a/app/models/group_label.rb b/app/models/group_label.rb
index 68841ace2e6..92c83b54861 100644
--- a/app/models/group_label.rb
+++ b/app/models/group_label.rb
@@ -8,8 +8,4 @@ class GroupLabel < Label
def subject_foreign_key
'group_id'
end
-
- def to_reference(source_project = nil, target_project = nil, format: :id)
- super(source_project, target_project, format: format)
- end
end
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
new file mode 100644
index 00000000000..7b6db2634b7
--- /dev/null
+++ b/app/models/group_milestone.rb
@@ -0,0 +1,19 @@
+class GroupMilestone < GlobalMilestone
+ attr_accessor :group
+
+ def self.build_collection(group, projects, params)
+ super(projects, params).each do |milestone|
+ milestone.group = group
+ end
+ end
+
+ def self.build(group, projects, title)
+ super(projects, title).tap do |milestone|
+ milestone.group = group if milestone
+ end
+ end
+
+ def issues_finder_params
+ { group_id: group.id }
+ end
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 6825553512f..65638d9a299 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -97,10 +97,10 @@ class Issue < ActiveRecord::Base
end
end
- def to_reference(from_project = nil)
+ def to_reference(from_project = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
- "#{project.to_reference(from_project)}#{reference}"
+ "#{project.to_reference(from_project, full: full)}#{reference}"
end
def referenced_merge_requests(current_user = nil)
diff --git a/app/models/key.rb b/app/models/key.rb
index 6f377f0e8ae..9c74ca84753 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -4,6 +4,8 @@ class Key < ActiveRecord::Base
include AfterCommitQueue
include Sortable
+ LAST_USED_AT_REFRESH_TIME = 1.day.to_i
+
belongs_to :user
before_validation :generate_fingerprint
@@ -49,6 +51,13 @@ class Key < ActiveRecord::Base
"key-#{id}"
end
+ def update_last_used_at
+ lease = Gitlab::ExclusiveLease.new("key_update_last_used_at:#{id}", timeout: LAST_USED_AT_REFRESH_TIME)
+ return unless lease.try_obtain
+
+ UseKeyWorker.perform_async(id)
+ end
+
def add_to_shell
GitlabShellWorker.perform_async(
:add_key,
diff --git a/app/models/label.rb b/app/models/label.rb
index d38c37344c9..5b6b9a7a736 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -26,6 +26,7 @@ class Label < ActiveRecord::Base
# Don't allow ',' for label titles
validates :title, presence: true, format: { with: /\A[^,]+\z/ }
validates :title, uniqueness: { scope: [:group_id, :project_id] }
+ validates :title, length: { maximum: 255 }
default_scope { order(title: :asc) }
@@ -146,17 +147,17 @@ class Label < ActiveRecord::Base
#
# Label.first.to_reference # => "~1"
# Label.first.to_reference(format: :name) # => "~\"bug\""
- # Label.first.to_reference(project, same_namespace_project) # => "gitlab-ce~1"
- # Label.first.to_reference(project, another_namespace_project) # => "gitlab-org/gitlab-ce~1"
+ # Label.first.to_reference(project, target_project: same_namespace_project) # => "gitlab-ce~1"
+ # Label.first.to_reference(project, target_project: another_namespace_project) # => "gitlab-org/gitlab-ce~1"
#
# Returns a String
#
- def to_reference(source_project = nil, target_project = nil, format: :id)
+ def to_reference(from_project = nil, target_project: nil, format: :id, full: false)
format_reference = label_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
- if source_project
- "#{source_project.to_reference(target_project)}#{reference}"
+ if from_project
+ "#{from_project.to_reference(target_project, full: full)}#{reference}"
else
reference
end
diff --git a/app/models/member.rb b/app/models/member.rb
index c585e0b450e..26a6054e00d 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -68,9 +68,9 @@ class Member < ActiveRecord::Base
after_create :send_request, if: :request?, unless: :importing?
after_create :create_notification_setting, unless: [:pending?, :importing?]
after_create :post_create_hook, unless: [:pending?, :importing?]
- after_create :refresh_member_authorized_projects, if: :importing?
after_update :post_update_hook, unless: [:pending?, :importing?]
after_destroy :post_destroy_hook, unless: :pending?
+ after_commit :refresh_member_authorized_projects
delegate :name, :username, :email, to: :user, prefix: true
@@ -147,8 +147,6 @@ class Member < ActiveRecord::Base
member.save
end
- UserProjectAccessChangedService.new(user.id).execute if user.is_a?(User)
-
member
end
@@ -275,23 +273,27 @@ class Member < ActiveRecord::Base
end
def post_create_hook
- UserProjectAccessChangedService.new(user.id).execute
system_hook_service.execute_hooks_for(self, :create)
end
def post_update_hook
- UserProjectAccessChangedService.new(user.id).execute if access_level_changed?
+ # override in sub class
end
def post_destroy_hook
- refresh_member_authorized_projects
system_hook_service.execute_hooks_for(self, :destroy)
end
+ # Refreshes authorizations of the current member.
+ #
+ # This method schedules a job using Sidekiq and as such **must not** be called
+ # in a transaction. Doing so can lead to the job running before the
+ # transaction has been committed, resulting in the job either throwing an
+ # error or not doing any meaningful work.
def refresh_member_authorized_projects
- # If user/source is being destroyed, project access are gonna be destroyed eventually
- # because of DB foreign keys, so we shouldn't bother with refreshing after each
- # member is destroyed through association
+ # If user/source is being destroyed, project access are going to be
+ # destroyed eventually because of DB foreign keys, so we shouldn't bother
+ # with refreshing after each member is destroyed through association
return if destroyed_by_association.present?
UserProjectAccessChangedService.new(user_id).execute
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 61845bf4036..6753504acff 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -91,6 +91,10 @@ class MergeRequest < ActiveRecord::Base
around_transition do |merge_request, transition, block|
Gitlab::Timeless.timeless(merge_request, &block)
end
+
+ after_transition unchecked: :cannot_be_merged do |merge_request, transition|
+ TodoService.new.merge_request_became_unmergeable(merge_request)
+ end
end
validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?]
@@ -175,10 +179,10 @@ class MergeRequest < ActiveRecord::Base
work_in_progress?(title) ? title : "WIP: #{title}"
end
- def to_reference(from_project = nil)
+ def to_reference(from_project = nil, full: false)
reference = "#{self.class.reference_prefix}#{iid}"
- "#{project.to_reference(from_project)}#{reference}"
+ "#{project.to_reference(from_project, full: full)}#{reference}"
end
def first_commit
@@ -221,7 +225,7 @@ class MergeRequest < ActiveRecord::Base
# true base commit, so we can't simply have `#diff_base_commit` fall back on
# this method.
def likely_diff_base_commit
- first_commit.parent || first_commit
+ first_commit.try(:parent) || first_commit
end
def diff_start_commit
@@ -861,9 +865,11 @@ class MergeRequest < ActiveRecord::Base
paths: paths
)
- active_diff_notes.each do |note|
- service.execute(note)
- Gitlab::Timeless.timeless(note, &:save)
+ transaction do
+ active_diff_notes.each do |note|
+ service.execute(note)
+ Gitlab::Timeless.timeless(note, &:save)
+ end
end
end
@@ -898,10 +904,22 @@ class MergeRequest < ActiveRecord::Base
end
def has_commits?
- commits_count > 0
+ merge_request_diff && commits_count > 0
end
def has_no_commits?
!has_commits?
end
+
+ def mergeable_with_slash_command?(current_user, autocomplete_precheck: false, last_diff_sha: nil)
+ return false unless can_be_merged_by?(current_user)
+
+ return true if autocomplete_precheck
+
+ return false unless mergeable?(skip_ci_check: true)
+ return false if head_pipeline && !(head_pipeline.success? || head_pipeline.active?)
+ return false if last_diff_sha != diff_head_sha
+
+ true
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index b8f36a2c958..dadb81f9b6e 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -1,7 +1,7 @@
class MergeRequestDiff < ActiveRecord::Base
include Sortable
include Importable
- include EncodingHelper
+ include Gitlab::Git::EncodingHelper
# Prevent store of diff if commits amount more then 500
COMMITS_SAFE_SIZE = 100
@@ -234,28 +234,28 @@ class MergeRequestDiff < ActiveRecord::Base
# and save it as array of hashes in st_diffs db field
def save_diffs
new_attributes = {}
- new_diffs = []
if commits.size.zero?
new_attributes[:state] = :empty
else
diff_collection = compare.diffs(Commit.max_diff_options)
-
- if diff_collection.overflow?
- # Set our state to 'overflow' to make the #empty? and #collected?
- # methods (generated by StateMachine) return false.
- new_attributes[:state] = :overflow
- end
-
- new_attributes[:real_size] = diff_collection.real_size
+ new_attributes[:real_size] = compare.diffs.real_size
if diff_collection.any?
new_diffs = dump_diffs(diff_collection)
new_attributes[:state] = :collected
end
+
+ new_attributes[:st_diffs] = new_diffs || []
+
+ # Set our state to 'overflow' to make the #empty? and #collected?
+ # methods (generated by StateMachine) return false.
+ #
+ # This attribution has to come at the end of the method so 'overflow'
+ # state does not get overridden by 'collected'.
+ new_attributes[:state] = :overflow if diff_collection.overflow?
end
- new_attributes[:st_diffs] = new_diffs
update_columns_serialized(new_attributes)
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 0dcfec89f14..7331000a9f2 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -118,11 +118,11 @@ class Milestone < ActiveRecord::Base
# Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1"
# Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1"
#
- def to_reference(from_project = nil, format: :iid)
+ def to_reference(from_project = nil, format: :iid, full: false)
format_reference = milestone_format_reference(format)
reference = "#{self.class.reference_prefix}#{format_reference}"
- "#{project.to_reference(from_project)}#{reference}"
+ "#{project.to_reference(from_project, full: full)}#{reference}"
end
def reference_link_text(from_project = nil)
@@ -202,4 +202,8 @@ class Milestone < ActiveRecord::Base
errors.add(:start_date, "Can't be greater than due date")
end
end
+
+ def issues_finder_params
+ { project_id: project_id }
+ end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index d41833de66f..67d8c1c2e4c 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -4,6 +4,7 @@ class Namespace < ActiveRecord::Base
include CacheMarkdownField
include Sortable
include Gitlab::ShellAdapter
+ include Gitlab::CurrentSettings
include Routable
cache_markdown_field :description, pipeline: :description
@@ -130,6 +131,8 @@ class Namespace < ActiveRecord::Base
Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
+ remove_exports!
+
# If repositories moved successfully we need to
# send update instructions to users.
# However we cannot allow rollback since we moved namespace dir
@@ -174,6 +177,10 @@ class Namespace < ActiveRecord::Base
end
end
+ def shared_runners_enabled?
+ projects.with_shared_runners.any?
+ end
+
def full_name
@full_name ||=
if parent
@@ -183,8 +190,26 @@ class Namespace < ActiveRecord::Base
end
end
- def parents
- @parents ||= parent ? parent.parents + [parent] : []
+ # Scopes the model on ancestors of the record
+ def ancestors
+ if parent_id
+ path = route.path
+ paths = []
+
+ until path.blank?
+ path = path.rpartition('/').first
+ paths << path
+ end
+
+ self.class.joins(:route).where('routes.path IN (?)', paths).reorder('routes.path ASC')
+ else
+ self.class.none
+ end
+ end
+
+ # Scopes the model on direct and indirect children of the record
+ def descendants
+ self.class.joins(:route).where('routes.path LIKE ?', "#{route.path}/%").reorder('routes.path ASC')
end
private
@@ -214,6 +239,8 @@ class Namespace < ActiveRecord::Base
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage_path, new_path)
end
end
+
+ remove_exports!
end
def refresh_access_of_projects_invited_groups
@@ -226,4 +253,20 @@ class Namespace < ActiveRecord::Base
def full_path_changed?
path_changed? || parent_id_changed?
end
+
+ def remove_exports!
+ Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete))
+ end
+
+ def export_path
+ File.join(Gitlab::ImportExport.storage_path, full_path_was)
+ end
+
+ def full_path_was
+ if parent
+ parent.full_path + '/' + path_was
+ else
+ path_was
+ end
+ end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 0c1b05dabf2..bf090a0438c 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -43,7 +43,8 @@ class Note < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true
delegate :title, to: :noteable, allow_nil: true
- validates :note, :project, presence: true
+ validates :note, presence: true
+ validates :project, presence: true, unless: :for_personal_snippet?
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
@@ -53,7 +54,7 @@ class Note < ActiveRecord::Base
validates :commit_id, presence: true, if: :for_commit?
validates :author, presence: true
- validate unless: [:for_commit?, :importing?] do |note|
+ validate unless: [:for_commit?, :importing?, :for_personal_snippet?] do |note|
unless note.noteable.try(:project) == note.project
errors.add(:invalid_project, 'Note and noteable project mismatch')
end
@@ -83,7 +84,7 @@ class Note < ActiveRecord::Base
after_initialize :ensure_discussion_id
before_validation :nullify_blank_type, :nullify_blank_line_code
before_validation :set_discussion_id
- after_save :keep_around_commit
+ after_save :keep_around_commit, unless: :for_personal_snippet?
class << self
def model_name
@@ -165,6 +166,14 @@ class Note < ActiveRecord::Base
noteable_type == "Snippet"
end
+ def for_personal_snippet?
+ noteable.is_a?(PersonalSnippet)
+ end
+
+ def skip_project_check?
+ for_personal_snippet?
+ end
+
# override to return commits, which are not active record
def noteable
if for_commit?
@@ -220,6 +229,10 @@ class Note < ActiveRecord::Base
note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
end
+ def to_ability_name
+ for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
+ end
+
private
def keep_around_commit
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 43fc218de2b..58f6214bea7 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -37,6 +37,10 @@ class NotificationSetting < ActiveRecord::Base
:success_pipeline
]
+ EXCLUDED_WATCHER_EVENTS = [
+ :success_pipeline
+ ]
+
store :events, accessors: EMAIL_EVENTS, coder: JSON
before_create :set_events
diff --git a/app/models/project.rb b/app/models/project.rb
index e0ffa7e7af7..59faf35e051 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -12,6 +12,7 @@ class Project < ActiveRecord::Base
include AfterCommitQueue
include CaseSensitivity
include TokenAuthenticatable
+ include ValidAttribute
include ProjectFeaturesCompatibility
include SelectForProjectAuthorization
include Routable
@@ -65,6 +66,8 @@ class Project < ActiveRecord::Base
end
end
+ after_validation :check_pending_delete
+
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :tags
@@ -118,8 +121,6 @@ class Project < ActiveRecord::Base
# Merge Requests for target project should be removed with it
has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id'
- # Merge requests from source project should be kept when source project was removed
- has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest
has_many :issues, dependent: :destroy
has_many :labels, dependent: :destroy, class_name: 'ProjectLabel'
has_many :services, dependent: :destroy
@@ -130,7 +131,7 @@ class Project < ActiveRecord::Base
has_many :hooks, dependent: :destroy, class_name: 'ProjectHook'
has_many :protected_branches, dependent: :destroy
- has_many :project_authorizations, dependent: :destroy
+ has_many :project_authorizations
has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User'
has_many :project_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source
alias_method :members, :project_members
@@ -223,6 +224,7 @@ class Project < ActiveRecord::Base
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) }
+ scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
@@ -589,8 +591,8 @@ class Project < ActiveRecord::Base
end
end
- def to_reference(from_project = nil)
- if cross_namespace_reference?(from_project)
+ def to_reference(from_project = nil, full: false)
+ if full || cross_namespace_reference?(from_project)
path_with_namespace
elsif cross_project_reference?(from_project)
path
@@ -609,10 +611,6 @@ class Project < ActiveRecord::Base
Gitlab::Routing.url_helpers.namespace_project_url(self.namespace, self)
end
- def web_url_without_protocol
- web_url.split('://')[1]
- end
-
def new_issue_address(author)
return unless Gitlab::IncomingEmail.supports_issue_creation? && author
@@ -1033,7 +1031,7 @@ class Project < ActiveRecord::Base
"refs/heads/#{branch}",
force: true)
repository.copy_gitattributes(branch)
- repository.expire_avatar_cache
+ repository.after_change_head
reload_default_branch
end
@@ -1099,12 +1097,20 @@ class Project < ActiveRecord::Base
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end
+ def shared_runners_available?
+ shared_runners_enabled?
+ end
+
+ def shared_runners
+ shared_runners_available? ? Ci::Runner.shared : Ci::Runner.none
+ end
+
def any_runners?(&block)
if runners.active.any?(&block)
return true
end
- shared_runners_enabled? && Ci::Runner.shared.active.any?(&block)
+ shared_runners.active.any?(&block)
end
def valid_runners_token?(token)
@@ -1324,4 +1330,21 @@ class Project < ActiveRecord::Base
stats = statistics || build_statistics
stats.update(namespace_id: namespace_id)
end
+
+ def check_pending_delete
+ return if valid_attribute?(:name) && valid_attribute?(:path)
+ return unless pending_delete_twin
+
+ %i[route route.path name path].each do |error|
+ errors.delete(error)
+ end
+
+ errors.add(:base, "The project is still being deleted. Please try again later.")
+ end
+
+ def pending_delete_twin
+ return false unless path
+
+ Project.unscoped.where(pending_delete: true).find_with_namespace(path_with_namespace)
+ end
end
diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb
index 6149c35cc61..5cb6b0c527d 100644
--- a/app/models/project_group_link.rb
+++ b/app/models/project_group_link.rb
@@ -16,8 +16,7 @@ class ProjectGroupLink < ActiveRecord::Base
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
validate :different_group
- after_create :refresh_group_members_authorized_projects
- after_destroy :refresh_group_members_authorized_projects
+ after_commit :refresh_group_members_authorized_projects
def self.access_options
Gitlab::Access.options
diff --git a/app/models/project_label.rb b/app/models/project_label.rb
index 82f47f0e8fd..313815e5869 100644
--- a/app/models/project_label.rb
+++ b/app/models/project_label.rb
@@ -16,8 +16,8 @@ class ProjectLabel < Label
'project_id'
end
- def to_reference(target_project = nil, format: :id)
- super(project, target_project, format: format)
+ def to_reference(target_project = nil, format: :id, full: false)
+ super(project, target_project: target_project, format: format, full: full)
end
private
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index 7c23b766763..3728f5642e4 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -25,7 +25,7 @@ You can create a Personal Access Token here:
http://app.asana.com/-/account_api'
end
- def to_param
+ def self.to_param
'asana'
end
@@ -44,7 +44,7 @@ http://app.asana.com/-/account_api'
]
end
- def supported_events
+ def self.supported_events
%w(push)
end
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index d839221d315..aeeff8917bf 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -12,7 +12,7 @@ class AssemblaService < Service
'Project Management Software (Source Commits Endpoint)'
end
- def to_param
+ def self.to_param
'assembla'
end
@@ -23,7 +23,7 @@ class AssemblaService < Service
]
end
- def supported_events
+ def self.supported_events
%w(push)
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index b5c76e4d4fe..400020ee04a 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -1,4 +1,6 @@
class BambooService < CiService
+ include ReactiveService
+
prop_accessor :bamboo_url, :build_key, :username, :password
validates :bamboo_url, presence: true, url: true, if: :activated?
@@ -38,7 +40,7 @@ class BambooService < CiService
'You must set up automatic revision labeling and a repository trigger in Bamboo.'
end
- def to_param
+ def self.to_param
'bamboo'
end
@@ -54,35 +56,46 @@ class BambooService < CiService
]
end
- def supported_events
- %w(push)
+ def build_page(sha, ref)
+ with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
end
- def build_info(sha)
- @response = get_path("rest/api/latest/result?label=#{sha}")
+ def commit_status(sha, ref)
+ with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
end
- def build_page(sha, ref)
- build_info(sha) if @response.nil? || !@response.code
+ def execute(data)
+ return unless supported_events.include?(data[:object_kind])
+
+ get_path("updateAndBuild.action?buildKey=#{build_key}")
+ end
+
+ def calculate_reactive_cache(sha, ref)
+ response = get_path("rest/api/latest/result?label=#{sha}")
+
+ { build_page: read_build_page(response), commit_status: read_commit_status(response) }
+ end
- if @response.code != 200 || @response['results']['results']['size'] == '0'
+ private
+
+ def read_build_page(response)
+ if response.code != 200 || response['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['results']['results']['result']['planResultKey']['key']
URI.join("#{bamboo_url}/", "browse/#{result_key}").to_s
end
end
- def commit_status(sha, ref)
- build_info(sha) if @response.nil? || !@response.code
- return :error unless @response.code == 200 || @response.code == 404
+ 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['results']['results']['size'] == '0'
'Pending'
else
- @response['results']['results']['result']['buildState']
+ response['results']['results']['result']['buildState']
end
if status.include?('Success')
@@ -96,14 +109,6 @@ class BambooService < CiService
end
end
- def execute(data)
- return unless supported_events.include?(data[:object_kind])
-
- get_path("updateAndBuild.action?buildKey=#{build_key}")
- end
-
- private
-
def build_url(path)
URI.join("#{bamboo_url}/", path).to_s
end
diff --git a/app/models/project_services/bugzilla_service.rb b/app/models/project_services/bugzilla_service.rb
index 338e685339a..046e2809f45 100644
--- a/app/models/project_services/bugzilla_service.rb
+++ b/app/models/project_services/bugzilla_service.rb
@@ -19,7 +19,7 @@ class BugzillaService < IssueTrackerService
end
end
- def to_param
+ def self.to_param
'bugzilla'
end
end
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index fe6d7aabb22..0956c4a4ede 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -1,6 +1,8 @@
require "addressable/uri"
class BuildkiteService < CiService
+ include ReactiveService
+
ENDPOINT = "https://buildkite.com"
prop_accessor :project_url, :token
@@ -22,10 +24,6 @@ class BuildkiteService < CiService
hook.save
end
- def supported_events
- %w(push)
- end
-
def execute(data)
return unless supported_events.include?(data[:object_kind])
@@ -33,13 +31,7 @@ class BuildkiteService < CiService
end
def commit_status(sha, ref)
- response = HTTParty.get(commit_status_path(sha), verify: false)
-
- if response.code == 200 && response['status']
- response['status']
- else
- :error
- end
+ with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
end
def commit_status_path(sha)
@@ -58,7 +50,7 @@ class BuildkiteService < CiService
'Continuous integration and deployments'
end
- def to_param
+ def self.to_param
'buildkite'
end
@@ -78,6 +70,19 @@ class BuildkiteService < CiService
]
end
+ def calculate_reactive_cache(sha, ref)
+ response = HTTParty.get(commit_status_path(sha), verify: false)
+
+ status =
+ if response.code == 200 && response['status']
+ response['status']
+ else
+ :error
+ end
+
+ { commit_status: status }
+ end
+
private
def webhook_token
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 201b94b065b..ebd21e37189 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -19,11 +19,11 @@ class BuildsEmailService < Service
'Email the builds status to a list of recipients.'
end
- def to_param
+ def self.to_param
'builds_email'
end
- def supported_events
+ def self.supported_events
%w(build)
end
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index 5af93860d09..0de59af5652 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -12,7 +12,7 @@ class CampfireService < Service
'Simple web-based real-time group chat'
end
- def to_param
+ def self.to_param
'campfire'
end
@@ -24,7 +24,7 @@ class CampfireService < Service
]
end
- def supported_events
+ def self.supported_events
%w(push)
end
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index b7ef44c3054..8468934425f 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -25,7 +25,7 @@ class ChatNotificationService < Service
valid?
end
- def supported_events
+ def self.supported_events
%w[push issue confidential_issue merge_request note tag_push
build pipeline wiki_page]
end
@@ -82,19 +82,19 @@ class ChatNotificationService < Service
def get_message(object_kind, data)
case object_kind
when "push", "tag_push"
- PushMessage.new(data)
+ ChatMessage::PushMessage.new(data)
when "issue"
- IssueMessage.new(data) unless is_update?(data)
+ ChatMessage::IssueMessage.new(data) unless is_update?(data)
when "merge_request"
- MergeMessage.new(data) unless is_update?(data)
+ ChatMessage::MergeMessage.new(data) unless is_update?(data)
when "note"
- NoteMessage.new(data)
+ ChatMessage::NoteMessage.new(data)
when "build"
- BuildMessage.new(data) if should_build_be_notified?(data)
+ ChatMessage::BuildMessage.new(data) if should_build_be_notified?(data)
when "pipeline"
- PipelineMessage.new(data) if should_pipeline_be_notified?(data)
+ ChatMessage::PipelineMessage.new(data) if should_pipeline_be_notified?(data)
when "wiki_page"
- WikiPageMessage.new(data)
+ ChatMessage::WikiPageMessage.new(data)
end
end
diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb
index 0bc160af604..2bcff541cc0 100644
--- a/app/models/project_services/chat_slash_commands_service.rb
+++ b/app/models/project_services/chat_slash_commands_service.rb
@@ -13,8 +13,8 @@ class ChatSlashCommandsService < Service
ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end
- def supported_events
- []
+ def self.supported_events
+ %w()
end
def can_test?
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 596c00705ad..82979c8bd34 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -8,19 +8,11 @@ class CiService < Service
self.respond_to?(:token) && self.token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token)
end
- def supported_events
+ def self.supported_events
%w(push)
end
- def merge_request_page(iid, sha, ref)
- commit_page(sha, ref)
- end
-
- def commit_page(sha, ref)
- build_page(sha, ref)
- end
-
- # Return complete url to merge_request page
+ # Return complete url to build page
#
# Ex.
# http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c
@@ -35,23 +27,6 @@ class CiService < Service
#
#
# Ex.
- # @service.merge_request_status(9, '13be4ac', 'dev')
- # # => 'success'
- #
- # @service.merge_request_status(10, '2abe4ac', 'dev)
- # # => 'running'
- #
- #
- def merge_request_status(iid, sha, ref)
- commit_status(sha, ref)
- end
-
- # Return string with build status or :error symbol
- #
- # Allowed states: 'success', 'failed', 'running', 'pending', 'skipped'
- #
- #
- # Ex.
# @service.commit_status('13be4ac', 'master')
# # => 'success'
#
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index b2f426dc2ac..dea915a4d05 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -23,7 +23,7 @@ class CustomIssueTrackerService < IssueTrackerService
end
end
- def to_param
+ def self.to_param
'custom_issue_tracker'
end
diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb
index ab353a1abe6..91a55514a9a 100644
--- a/app/models/project_services/deployment_service.rb
+++ b/app/models/project_services/deployment_service.rb
@@ -5,8 +5,8 @@
class DeploymentService < Service
default_value_for :category, 'deployment'
- def supported_events
- []
+ def self.supported_events
+ %w()
end
def predefined_variables
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index adc78a427ee..0a217d8caba 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -1,4 +1,6 @@
class DroneCiService < CiService
+ include ReactiveService
+
prop_accessor :drone_url, :token
boolean_accessor :enable_ssl_verification
@@ -30,18 +32,10 @@ class DroneCiService < CiService
true
end
- def supported_events
+ def self.supported_events
%w(push merge_request tag_push)
end
- def merge_request_status_path(iid, sha = nil, ref = nil)
- url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/pulls/#{iid}",
- "?access_token=#{token}"]
-
- URI.join(*url).to_s
- end
-
def commit_status_path(sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/commits/#{sha}",
@@ -50,54 +44,34 @@ class DroneCiService < CiService
URI.join(*url).to_s
end
- def merge_request_status(iid, sha, ref)
- response = HTTParty.get(merge_request_status_path(iid), verify: enable_ssl_verification)
-
- if response.code == 200 and response['status']
- case response['status']
- when 'killed'
- :canceled
- when 'failure', 'error'
- # Because drone return error if some test env failed
- :failed
- else
- response["status"]
- end
- else
- :error
- end
- rescue Errno::ECONNREFUSED
- :error
+ def commit_status(sha, ref)
+ with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
end
- def commit_status(sha, ref)
+ def calculate_reactive_cache(sha, ref)
response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification)
- if response.code == 200 and response['status']
- case response['status']
- when 'killed'
- :canceled
- when 'failure', 'error'
- # Because drone return error if some test env failed
- :failed
+ status =
+ if response.code == 200 and response['status']
+ case response['status']
+ when 'killed'
+ :canceled
+ when 'failure', 'error'
+ # Because drone return error if some test env failed
+ :failed
+ else
+ response["status"]
+ end
else
- response["status"]
+ :error
end
- else
- :error
- end
- rescue Errno::ECONNREFUSED
- :error
- end
- def merge_request_page(iid, sha, ref)
- url = [drone_url,
- "gitlab/#{project.namespace.path}/#{project.path}/redirect/pulls/#{iid}"]
-
- URI.join(*url).to_s
+ { commit_status: status }
+ rescue Errno::ECONNREFUSED
+ { commit_status: :error }
end
- def commit_page(sha, ref)
+ def build_page(sha, ref)
url = [drone_url,
"gitlab/#{project.namespace.path}/#{project.path}/redirect/commits/#{sha}",
"?branch=#{URI::encode(ref.to_s)}"]
@@ -105,14 +79,6 @@ class DroneCiService < CiService
URI.join(*url).to_s
end
- def commit_coverage(sha, ref)
- nil
- end
-
- def build_page(sha, ref)
- commit_page(sha, ref)
- end
-
def title
'Drone CI'
end
@@ -121,7 +87,7 @@ class DroneCiService < CiService
'Drone is a Continuous Integration platform built on Docker, written in Go'
end
- def to_param
+ def self.to_param
'drone_ci'
end
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index 79285cbd26d..f4f913ee0b6 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -12,11 +12,11 @@ class EmailsOnPushService < Service
'Email the commits and diff of each push to a list of recipients.'
end
- def to_param
+ def self.to_param
'emails_on_push'
end
- def supported_events
+ def self.supported_events
%w(push tag_push)
end
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index d7b6e505191..bdf6fa6a586 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -13,7 +13,7 @@ class ExternalWikiService < Service
'Replaces the link to the internal wiki with a link to an external wiki.'
end
- def to_param
+ def self.to_param
'external_wiki'
end
@@ -29,4 +29,8 @@ class ExternalWikiService < Service
nil
end
end
+
+ def self.supported_events
+ %w()
+ end
end
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index dd00275187f..10a13c3fbdc 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -12,7 +12,7 @@ class FlowdockService < Service
'Flowdock is a collaboration web app for technical teams.'
end
- def to_param
+ def self.to_param
'flowdock'
end
@@ -22,7 +22,7 @@ class FlowdockService < Service
]
end
- def supported_events
+ def self.supported_events
%w(push)
end
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 598aca5e06d..f271e1f1739 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -12,7 +12,7 @@ class GemnasiumService < Service
'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
end
- def to_param
+ def self.to_param
'gemnasium'
end
@@ -23,7 +23,7 @@ class GemnasiumService < Service
]
end
- def supported_events
+ def self.supported_events
%w(push)
end
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 6bd8d4ec568..ad4eb9536e1 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -7,7 +7,7 @@ class GitlabIssueTrackerService < IssueTrackerService
default_value_for :default, true
- def to_param
+ def self.to_param
'gitlab'
end
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 915f6fed74c..72da219df28 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -27,7 +27,7 @@ class HipchatService < Service
'Private group chat and IM'
end
- def to_param
+ def self.to_param
'hipchat'
end
@@ -45,7 +45,7 @@ class HipchatService < Service
]
end
- def supported_events
+ def self.supported_events
%w(push issue confidential_issue merge_request note tag_push build)
end
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 7355918feab..5d93064f9b3 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -17,11 +17,11 @@ class IrkerService < Service
'gateway.'
end
- def to_param
+ def self.to_param
'irker'
end
- def supported_events
+ def self.supported_events
%w(push)
end
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index bce2cdd5516..9e65fdbf9d6 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -57,7 +57,7 @@ class IssueTrackerService < Service
end
end
- def supported_events
+ def self.supported_events
%w(push)
end
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 2d969d2fcb6..2ac76e97de0 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -12,7 +12,7 @@ class JiraService < IssueTrackerService
# This is confusing, but JiraService does not really support these events.
# The values here are required to display correct options in the service
# configuration screen.
- def supported_events
+ def self.supported_events
%w(commit merge_request)
end
@@ -81,7 +81,7 @@ class JiraService < IssueTrackerService
end
end
- def to_param
+ def self.to_param
'jira'
end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 085125ca9dc..fa3cedc4354 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -52,7 +52,7 @@ class KubernetesService < DeploymentService
'deployments with `app=$CI_ENVIRONMENT_SLUG`'
end
- def to_param
+ def self.to_param
'kubernetes'
end
diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb
index ee8a0b55275..4ebc5318da1 100644
--- a/app/models/project_services/mattermost_service.rb
+++ b/app/models/project_services/mattermost_service.rb
@@ -7,7 +7,7 @@ class MattermostService < ChatNotificationService
'Receive event notifications in Mattermost'
end
- def to_param
+ def self.to_param
'mattermost'
end
@@ -36,6 +36,6 @@ class MattermostService < ChatNotificationService
end
def default_channel_placeholder
- "#town-square"
+ "town-square"
end
end
diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb
index 2cb481182d7..50a011db74e 100644
--- a/app/models/project_services/mattermost_slash_commands_service.rb
+++ b/app/models/project_services/mattermost_slash_commands_service.rb
@@ -15,7 +15,7 @@ class MattermostSlashCommandsService < ChatSlashCommandsService
"Perform common operations on GitLab in Mattermost"
end
- def to_param
+ def self.to_param
'mattermost_slash_commands'
end
diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb
index 745f9bd1b43..ac617f409d9 100644
--- a/app/models/project_services/pipelines_email_service.rb
+++ b/app/models/project_services/pipelines_email_service.rb
@@ -15,11 +15,11 @@ class PipelinesEmailService < Service
'Email the pipelines status to a list of recipients.'
end
- def to_param
+ def self.to_param
'pipelines_email'
end
- def supported_events
+ def self.supported_events
%w[pipeline]
end
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index 5301f9fa0ff..9cc642591f4 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -14,7 +14,7 @@ class PivotaltrackerService < Service
'Project Management Software (Source Commits Endpoint)'
end
- def to_param
+ def self.to_param
'pivotaltracker'
end
@@ -34,7 +34,7 @@ class PivotaltrackerService < Service
]
end
- def supported_events
+ def self.supported_events
%w(push)
end
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 3dd878e4c7d..a963d27a376 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -13,7 +13,7 @@ class PushoverService < Service
'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.'
end
- def to_param
+ def self.to_param
'pushover'
end
@@ -61,7 +61,7 @@ class PushoverService < Service
]
end
- def supported_events
+ def self.supported_events
%w(push)
end
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index f9da273cf08..6acf611eba5 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -19,7 +19,7 @@ class RedmineService < IssueTrackerService
end
end
- def to_param
+ def self.to_param
'redmine'
end
end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 76d233a3cca..f77d2d7c60b 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -7,7 +7,7 @@ class SlackService < ChatNotificationService
'Receive event notifications in Slack'
end
- def to_param
+ def self.to_param
'slack'
end
diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb
index 5a7cc0fb329..c34991e4262 100644
--- a/app/models/project_services/slack_slash_commands_service.rb
+++ b/app/models/project_services/slack_slash_commands_service.rb
@@ -9,7 +9,7 @@ class SlackSlashCommandsService < ChatSlashCommandsService
"Perform common operations on GitLab in Slack"
end
- def to_param
+ def self.to_param
'slack_slash_commands'
end
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index a4a967c9bc9..cbaffb8ce48 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -1,4 +1,6 @@
class TeamcityService < CiService
+ include ReactiveService
+
prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url, presence: true, url: true, if: :activated?
@@ -41,14 +43,10 @@ class TeamcityService < CiService
'requests build, that setting is in the vsc root advanced settings.'
end
- def to_param
+ def self.to_param
'teamcity'
end
- def supported_events
- %w(push)
- end
-
def fields
[
{ type: 'text', name: 'teamcity_url',
@@ -61,43 +59,18 @@ class TeamcityService < CiService
]
end
- def build_info(sha)
- @response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
- end
-
def build_page(sha, ref)
- build_info(sha) if @response.nil? || !@response.code
-
- if @response.code != 200
- # If actual build link can't be determined,
- # send user to build summary page.
- build_url("viewLog.html?buildTypeId=#{build_type}")
- else
- # If actual build link is available, go to build result page.
- built_id = @response['build']['id']
- build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
- end
+ with_reactive_cache(sha, ref) {|cached| cached[:build_page] }
end
def commit_status(sha, ref)
- build_info(sha) if @response.nil? || !@response.code
- return :error unless @response.code == 200 || @response.code == 404
+ with_reactive_cache(sha, ref) {|cached| cached[:commit_status] }
+ end
- status = if @response.code == 404
- 'Pending'
- else
- @response['build']['status']
- end
+ def calculate_reactive_cache(sha, ref)
+ response = get_path("httpAuth/app/rest/builds/branch:unspecified:any,number:#{sha}")
- if status.include?('SUCCESS')
- 'success'
- elsif status.include?('FAILURE')
- 'failed'
- elsif status.include?('Pending')
- 'pending'
- else
- :error
- end
+ { build_page: read_build_page(response), commit_status: read_commit_status(response) }
end
def execute(data)
@@ -122,6 +95,40 @@ class TeamcityService < CiService
private
+ def read_build_page(response)
+ if response.code != 200
+ # If actual build link can't be determined,
+ # send user to build summary page.
+ build_url("viewLog.html?buildTypeId=#{build_type}")
+ else
+ # If actual build link is available, go to build result page.
+ built_id = response['build']['id']
+ build_url("viewLog.html?buildId=#{built_id}&buildTypeId=#{build_type}")
+ end
+ end
+
+ def read_commit_status(response)
+ return :error unless response.code == 200 || response.code == 404
+
+ status = if response.code == 404
+ 'Pending'
+ else
+ response['build']['status']
+ end
+
+ return :error unless status.present?
+
+ if status.include?('SUCCESS')
+ 'success'
+ elsif status.include?('FAILURE')
+ 'failed'
+ elsif status.include?('Pending')
+ 'pending'
+ else
+ :error
+ end
+ end
+
def build_url(path)
URI.join("#{teamcity_url}/", path).to_s
end
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 2270ac75071..06abd406523 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -25,8 +25,9 @@ class ProjectStatistics < ActiveRecord::Base
self.commit_count = project.repository.commit_count
end
+ # Repository#size needs to be converted from MB to Byte.
def update_repository_size
- self.repository_size = project.repository.size
+ self.repository_size = project.repository.size * 1.megabyte
end
def update_lfs_objects_size
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 3266e9c75f0..d77b7692d75 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -439,6 +439,11 @@ class Repository
expire_content_cache
end
+ # Runs code after the HEAD of a repository is changed.
+ def after_change_head
+ expire_method_caches(METHOD_CACHES_FOR_FILE_TYPES.keys)
+ end
+
# Runs code after a repository has been forked/imported.
def after_import
expire_content_cache
@@ -1183,7 +1188,18 @@ class Repository
end
def tags_sorted_by_committed_date
- tags.sort_by { |tag| tag.dereferenced_target.committed_date }
+ tags.sort_by do |tag|
+ # Annotated tags can point to any object (e.g. a blob), but generally
+ # tags point to a commit. If we don't have a commit, then just default
+ # to putting the tag at the end of the list.
+ target = tag.dereferenced_target
+
+ if target
+ target.committed_date
+ else
+ Time.now
+ end
+ end
end
def keep_around_ref_name(sha)
diff --git a/app/models/route.rb b/app/models/route.rb
index caf596efa79..dd171fdb069 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -8,15 +8,16 @@ class Route < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
- after_update :rename_children, if: :path_changed?
+ after_update :rename_descendants, if: :path_changed?
- def rename_children
+ def rename_descendants
# We update each row separately because MySQL does not have regexp_replace.
# rubocop:disable Rails/FindEach
Route.where('path LIKE ?', "#{path_was}/%").each do |route|
# Note that update column skips validation and callbacks.
- # We need this to avoid recursive call of rename_children method
+ # We need this to avoid recursive call of rename_descendants method
route.update_column(:path, route.path.sub(path_was, path))
end
+ # rubocop:enable Rails/FindEach
end
end
diff --git a/app/models/service.rb b/app/models/service.rb
index 19ef3ba9c23..043be222f3a 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -76,6 +76,11 @@ class Service < ActiveRecord::Base
def to_param
# implement inside child
+ self.class.to_param
+ end
+
+ def self.to_param
+ raise NotImplementedError
end
def fields
@@ -92,7 +97,11 @@ class Service < ActiveRecord::Base
end
def event_names
- supported_events.map { |event| "#{event}_events" }
+ self.class.event_names
+ end
+
+ def self.event_names
+ self.supported_events.map { |event| "#{event}_events" }
end
def event_field(event)
@@ -104,6 +113,10 @@ class Service < ActiveRecord::Base
end
def supported_events
+ self.class.supported_events
+ end
+
+ def self.supported_events
%w(push tag_push issue confidential_issue merge_request wiki_page)
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 98ccf5f331f..771a7350556 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -64,11 +64,11 @@ class Snippet < ActiveRecord::Base
@link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end
- def to_reference(from_project = nil)
+ def to_reference(from_project = nil, full: false)
reference = "#{self.class.reference_prefix}#{id}"
if project.present?
- "#{project.to_reference(from_project)}#{reference}"
+ "#{project.to_reference(from_project, full: full)}#{reference}"
else
reference
end
diff --git a/app/models/timelog.rb b/app/models/timelog.rb
new file mode 100644
index 00000000000..f768c4e3da5
--- /dev/null
+++ b/app/models/timelog.rb
@@ -0,0 +1,6 @@
+class Timelog < ActiveRecord::Base
+ validates :time_spent, :user, presence: true
+
+ belongs_to :trackable, polymorphic: true
+ belongs_to :user
+end
diff --git a/app/models/todo.rb b/app/models/todo.rb
index f5ade1cc293..4c99aa0d3be 100644
--- a/app/models/todo.rb
+++ b/app/models/todo.rb
@@ -6,13 +6,15 @@ class Todo < ActiveRecord::Base
BUILD_FAILED = 3
MARKED = 4
APPROVAL_REQUIRED = 5 # This is an EE-only feature
+ UNMERGEABLE = 6
ACTION_NAMES = {
ASSIGNED => :assigned,
MENTIONED => :mentioned,
BUILD_FAILED => :build_failed,
MARKED => :marked,
- APPROVAL_REQUIRED => :approval_required
+ APPROVAL_REQUIRED => :approval_required,
+ UNMERGEABLE => :unmergeable
}
belongs_to :author, class_name: "User"
@@ -66,6 +68,10 @@ class Todo < ActiveRecord::Base
end
end
+ def unmergeable?
+ action == UNMERGEABLE
+ end
+
def build_failed?
action == BUILD_FAILED
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 899a89a4eaa..54f5388eb2c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -73,7 +73,7 @@ class User < ActiveRecord::Base
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :users_star_projects, dependent: :destroy
has_many :starred_projects, through: :users_star_projects, source: :project
- has_many :project_authorizations, dependent: :destroy
+ has_many :project_authorizations
has_many :authorized_projects, through: :project_authorizations, source: :project
has_many :snippets, dependent: :destroy, foreign_key: :author_id
@@ -99,6 +99,7 @@ class User < ActiveRecord::Base
#
# Note: devise :validatable above adds validations for :email and :password
validates :name, presence: true
+ validates_confirmation_of :email
validates :notification_email, presence: true
validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
@@ -178,8 +179,8 @@ class User < ActiveRecord::Base
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members WHERE user_id IS NOT NULL AND requested_at IS NULL)') }
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
- scope :order_recent_sign_in, -> { reorder(last_sign_in_at: :desc) }
- scope :order_oldest_sign_in, -> { reorder(last_sign_in_at: :asc) }
+ scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'DESC')) }
+ scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('last_sign_in_at', 'ASC')) }
def self.with_two_factor
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id").
@@ -332,7 +333,7 @@ class User < ActiveRecord::Base
username
end
- def to_reference(_from_project = nil, _target_project = nil)
+ def to_reference(_from_project = nil, target_project: nil, full: nil)
"#{self.class.reference_prefix}#{username}"
end
@@ -438,12 +439,21 @@ class User < ActiveRecord::Base
Group.where("namespaces.id IN (#{union.to_sql})")
end
+ def nested_groups
+ Group.member_descendants(id)
+ end
+
+ def nested_projects
+ Project.joins(:namespace).where('namespaces.parent_id IS NOT NULL').
+ member_descendants(id)
+ end
+
def refresh_authorized_projects
Users::RefreshAuthorizedProjectsService.new(self).execute
end
def remove_project_authorizations(project_ids)
- project_authorizations.where(id: project_ids).delete_all
+ project_authorizations.where(project_id: project_ids).delete_all
end
def set_authorized_projects_column
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 118c100ca11..b9f1c29c32e 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -53,6 +53,10 @@ class BasePolicy
def self.class_for(subject)
return GlobalPolicy if subject.nil?
+ if subject.class.try(:presenter?)
+ subject = subject.subject
+ end
+
subject.class.ancestors.each do |klass|
next unless klass.name
diff --git a/app/presenters/README.md b/app/presenters/README.md
new file mode 100644
index 00000000000..a4d592b54d6
--- /dev/null
+++ b/app/presenters/README.md
@@ -0,0 +1,154 @@
+# Presenters
+
+This type of class is responsible for giving the view an object which defines
+**view-related logic/data methods**. It is usually useful to extract such
+methods from models to presenters.
+
+## When to use a presenter?
+
+### When your view is full of logic
+
+When your view is full of logic (`if`, `else`, `select` on arrays etc.), it's
+time to create a presenter!
+
+### When your model has a lot of view-related logic/data methods
+
+When your model has a lot of view-related logic/data methods, you can easily
+move them to a presenter.
+
+## Why are we using presenters instead of helpers?
+
+We don't use presenters to generate complex view output that would rely on helpers.
+
+Presenters should be used for:
+
+- Data and logic methods that can be pulled & combined into single methods from
+ view. This can include loops extracted from views too. A good example is
+ https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7073/diffs.
+- Data and logic methods that can be pulled from models.
+- Simple text output methods: it's ok if the method returns a string, but not a
+ whole DOM element for which we'd need HAML, a view context, helpers etc.
+
+## Why use presenters instead of model concerns?
+
+We should strive to follow the single-responsibility principle, and view-related
+logic/data methods are definitely not the responsibility of models!
+
+Another reason is as follows:
+
+> Avoid using concerns and use presenters instead. Why? After all, concerns seem
+to be a core part of Rails and can DRY up code when shared among multiple models.
+Nonetheless, the main issue is that concerns don’t make the model object more
+cohesive. The code is just better organized. In other words, there’s no real
+change to the API of the model.
+
+– https://www.toptal.com/ruby-on-rails/decoupling-rails-components
+
+## Benefits
+
+By moving pure view-related logic/data methods from models & views to presenters,
+we gain the following benefits:
+
+- rules are more explicit and centralized in the presenter => improves security
+- testing is easier and faster as presenters are Plain Old Ruby Object (PORO)
+- views are more readable and maintainable
+- decreases number of CE -> EE merge conflicts since code is in separate files
+- moves the conflicts from views (not always obvious) to presenters (a lot easier to resolve)
+
+## What not to do with presenters?
+
+- Don't use helpers in presenters. Presenters are not aware of the view context.
+- Don't generate complex DOM elements, forms etc. with presenters. Presenters
+ can return simple data as texts, and URLs using URL helpers from
+ `Gitlab::Routing` but nothing much more fancy.
+
+## Implementation
+
+### Presenter definition
+
+Every presenter should inherit from `Gitlab::View::Presenter::Simple`, which
+provides a `.presents` method which allows you to define an accessor for the
+presented object. It also includes common helpers like `Gitlab::Routing` and
+`Gitlab::Allowable`.
+
+```ruby
+class LabelPresenter < Gitlab::View::Presenter::Simple
+ presents :label
+
+ def text_color
+ label.color.to_s
+ end
+
+ def to_partial_path
+ 'projects/labels/show'
+ end
+end
+```
+
+In some cases, it can be more practical to transparently delegate all missing
+method calls to the presented object, in these cases, you can make your
+presenter inherit from `Gitlab::View::Presenter::Delegated`:
+
+```ruby
+class LabelPresenter < Gitlab::View::Presenter::Delegated
+ presents :label
+
+ def text_color
+ # color is delegated to label
+ color.to_s
+ end
+
+ def to_partial_path
+ 'projects/labels/show'
+ end
+end
+```
+
+### Presenter instantiation
+
+Instantiation must be done via the `Gitlab::View::Presenter::Factory` class which
+detects the presenter based on the presented subject's class.
+
+```ruby
+class Projects::LabelsController < Projects::ApplicationController
+ def edit
+ @label = Gitlab::View::Presenter::Factory
+ .new(@label, current_user: current_user)
+ .fabricate!
+ end
+end
+```
+
+You can also include the `Presentable` concern in the model:
+
+```ruby
+class Label
+ include Presentable
+end
+```
+
+and then in the controller:
+
+```ruby
+class Projects::LabelsController < Projects::ApplicationController
+ def edit
+ @label = @label.present(current_user: current_user)
+ end
+end
+```
+
+### Presenter usage
+
+```ruby
+%div{ class: @label.text_color }
+ = render partial: @label, label: @label
+```
+
+You can also present the model in the view:
+
+```ruby
+- label = @label.present(current_user: current_user)
+
+%div{ class: label.text_color }
+ = render partial: label, label: label
+```
diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb
new file mode 100644
index 00000000000..ed72ed14d72
--- /dev/null
+++ b/app/presenters/ci/build_presenter.rb
@@ -0,0 +1,15 @@
+module Ci
+ class BuildPresenter < Gitlab::View::Presenter::Delegated
+ presents :build
+
+ def erased_by_user?
+ # Build can be erased through API, therefore it does not have
+ # `erased_by` user assigned in that case.
+ erased? && erased_by
+ end
+
+ def erased_by_name
+ erased_by.name if erased_by_user?
+ end
+ end
+end
diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb
new file mode 100644
index 00000000000..a559d0850c4
--- /dev/null
+++ b/app/serializers/analytics_stage_entity.rb
@@ -0,0 +1,10 @@
+class AnalyticsStageEntity < Grape::Entity
+ include EntityDateHelper
+
+ expose :title
+ expose :description
+
+ expose :median, as: :value do |stage|
+ stage.median && !stage.median.zero? ? distance_of_time_in_words(stage.median) : nil
+ end
+end
diff --git a/app/serializers/analytics_stage_serializer.rb b/app/serializers/analytics_stage_serializer.rb
new file mode 100644
index 00000000000..613cf6874d8
--- /dev/null
+++ b/app/serializers/analytics_stage_serializer.rb
@@ -0,0 +1,3 @@
+class AnalyticsStageSerializer < BaseSerializer
+ entity AnalyticsStageEntity
+end
diff --git a/app/serializers/analytics_summary_entity.rb b/app/serializers/analytics_summary_entity.rb
new file mode 100644
index 00000000000..91803ec07f5
--- /dev/null
+++ b/app/serializers/analytics_summary_entity.rb
@@ -0,0 +1,7 @@
+class AnalyticsSummaryEntity < Grape::Entity
+ expose :value, safe: true
+
+ expose :title do |object|
+ object.title.pluralize(object.value)
+ end
+end
diff --git a/app/serializers/analytics_summary_serializer.rb b/app/serializers/analytics_summary_serializer.rb
new file mode 100644
index 00000000000..c87a24aa47c
--- /dev/null
+++ b/app/serializers/analytics_summary_serializer.rb
@@ -0,0 +1,3 @@
+class AnalyticsSummarySerializer < BaseSerializer
+ entity AnalyticsSummaryEntity
+end
diff --git a/app/serializers/build_action_entity.rb b/app/serializers/build_action_entity.rb
new file mode 100644
index 00000000000..184f5fd4b52
--- /dev/null
+++ b/app/serializers/build_action_entity.rb
@@ -0,0 +1,14 @@
+class BuildActionEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :name do |build|
+ build.name
+ end
+
+ expose :path do |build|
+ play_namespace_project_build_path(
+ build.project.namespace,
+ build.project,
+ build)
+ end
+end
diff --git a/app/serializers/build_artifact_entity.rb b/app/serializers/build_artifact_entity.rb
new file mode 100644
index 00000000000..8b643d8e783
--- /dev/null
+++ b/app/serializers/build_artifact_entity.rb
@@ -0,0 +1,14 @@
+class BuildArtifactEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :name do |build|
+ build.name
+ end
+
+ expose :path do |build|
+ download_namespace_project_build_artifacts_path(
+ build.project.namespace,
+ build.project,
+ build)
+ end
+end
diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb
index acc20f6dc52..31763955f97 100644
--- a/app/serializers/commit_entity.rb
+++ b/app/serializers/commit_entity.rb
@@ -3,17 +3,21 @@ class CommitEntity < API::Entities::RepoCommit
expose :author, using: UserEntity
+ expose :author_gravatar_url do |commit|
+ GravatarService.new.execute(commit.author_email)
+ end
+
expose :commit_url do |commit|
- namespace_project_tree_url(
+ namespace_project_commit_url(
request.project.namespace,
request.project,
- id: commit.id)
+ commit)
end
expose :commit_path do |commit|
- namespace_project_tree_path(
+ namespace_project_commit_path(
request.project.namespace,
request.project,
- id: commit.id)
+ commit)
end
end
diff --git a/app/serializers/issuable_entity.rb b/app/serializers/issuable_entity.rb
index 17c9160cb19..29aecb50849 100644
--- a/app/serializers/issuable_entity.rb
+++ b/app/serializers/issuable_entity.rb
@@ -13,4 +13,8 @@ class IssuableEntity < Grape::Entity
expose :created_at
expose :updated_at
expose :deleted_at
+ expose :time_estimate
+ expose :total_time_spent
+ expose :human_time_estimate
+ expose :human_total_time_spent
end
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
new file mode 100644
index 00000000000..61f0f11d7d2
--- /dev/null
+++ b/app/serializers/pipeline_entity.rb
@@ -0,0 +1,85 @@
+class PipelineEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :id
+ expose :user, using: UserEntity
+
+ expose :path do |pipeline|
+ namespace_project_pipeline_path(
+ pipeline.project.namespace,
+ pipeline.project,
+ pipeline)
+ end
+
+ expose :details do
+ expose :status do |pipeline, options|
+ StatusEntity.represent(
+ pipeline.detailed_status(request.user),
+ options)
+ end
+
+ expose :duration
+ expose :finished_at
+ expose :stages, using: StageEntity
+ expose :artifacts, using: BuildArtifactEntity
+ expose :manual_actions, using: BuildActionEntity
+ end
+
+ expose :flags do
+ expose :latest?, as: :latest
+ expose :triggered?, as: :triggered
+ expose :stuck?, as: :stuck
+ expose :has_yaml_errors?, as: :yaml_errors
+ expose :can_retry?, as: :retryable
+ expose :can_cancel?, as: :cancelable
+ end
+
+ expose :ref do
+ expose :name do |pipeline|
+ pipeline.ref
+ end
+
+ expose :path do |pipeline|
+ if pipeline.ref
+ namespace_project_tree_path(
+ pipeline.project.namespace,
+ pipeline.project,
+ id: pipeline.ref)
+ end
+ end
+
+ expose :tag?, as: :tag
+ expose :branch?, as: :branch
+ end
+
+ expose :commit, using: CommitEntity
+ expose :yaml_errors, if: ->(pipeline, _) { pipeline.has_yaml_errors? }
+
+ expose :retry_path, if: proc { can_retry? } do |pipeline|
+ retry_namespace_project_pipeline_path(pipeline.project.namespace,
+ pipeline.project,
+ pipeline.id)
+ end
+
+ expose :cancel_path, if: proc { can_cancel? } do |pipeline|
+ cancel_namespace_project_pipeline_path(pipeline.project.namespace,
+ pipeline.project,
+ pipeline.id)
+ end
+
+ expose :created_at, :updated_at
+
+ private
+
+ alias_method :pipeline, :object
+
+ def can_retry?
+ pipeline.retryable? &&
+ can?(request.user, :update_pipeline, pipeline)
+ end
+
+ def can_cancel?
+ pipeline.cancelable? &&
+ can?(request.user, :update_pipeline, pipeline)
+ end
+end
diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb
new file mode 100644
index 00000000000..cfa86cc2553
--- /dev/null
+++ b/app/serializers/pipeline_serializer.rb
@@ -0,0 +1,40 @@
+class PipelineSerializer < BaseSerializer
+ entity PipelineEntity
+ class InvalidResourceError < StandardError; end
+ include API::Helpers::Pagination
+ Struct.new('Pagination', :request, :response)
+
+ def represent(resource, opts = {})
+ if paginated?
+ raise InvalidResourceError unless resource.respond_to?(:page)
+
+ super(paginate(resource.includes(project: :namespace)), opts)
+ else
+ super(resource, opts)
+ end
+ end
+
+ def paginated?
+ defined?(@pagination)
+ end
+
+ def with_pagination(request, response)
+ tap { @pagination = Struct::Pagination.new(request, response) }
+ end
+
+ private
+
+ # Methods needed by `API::Helpers::Pagination`
+ #
+ def params
+ @pagination.request.query_parameters
+ end
+
+ def request
+ @pagination.request
+ end
+
+ def header(header, value)
+ @pagination.response.headers[header] = value
+ end
+end
diff --git a/app/serializers/request_aware_entity.rb b/app/serializers/request_aware_entity.rb
index e159d750cb7..3039014aaaa 100644
--- a/app/serializers/request_aware_entity.rb
+++ b/app/serializers/request_aware_entity.rb
@@ -2,14 +2,11 @@ module RequestAwareEntity
extend ActiveSupport::Concern
included do
- include Gitlab::Routing.url_helpers
+ include Gitlab::Routing
+ include Gitlab::Allowable
end
def request
- @options.fetch(:request)
- end
-
- def can?(object, action, subject)
- Ability.allowed?(object, action, subject)
+ options.fetch(:request)
end
end
diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb
new file mode 100644
index 00000000000..7a047bdc712
--- /dev/null
+++ b/app/serializers/stage_entity.rb
@@ -0,0 +1,38 @@
+class StageEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :name
+
+ expose :title do |stage|
+ "#{stage.name}: #{detailed_status.label}"
+ end
+
+ expose :detailed_status,
+ as: :status,
+ with: StatusEntity
+
+ expose :path do |stage|
+ namespace_project_pipeline_path(
+ stage.pipeline.project.namespace,
+ stage.pipeline.project,
+ stage.pipeline,
+ anchor: stage.name)
+ end
+
+ expose :dropdown_path do |stage|
+ stage_namespace_project_pipeline_path(
+ stage.pipeline.project.namespace,
+ stage.pipeline.project,
+ stage.pipeline,
+ stage: stage.name,
+ format: :json)
+ end
+
+ private
+
+ alias_method :stage, :object
+
+ def detailed_status
+ stage.detailed_status(request.user)
+ end
+end
diff --git a/app/serializers/status_entity.rb b/app/serializers/status_entity.rb
new file mode 100644
index 00000000000..47066bebfb1
--- /dev/null
+++ b/app/serializers/status_entity.rb
@@ -0,0 +1,8 @@
+class StatusEntity < Grape::Entity
+ include RequestAwareEntity
+
+ expose :icon, :text, :label, :group
+
+ expose :has_details?, as: :has_details
+ expose :details_path
+end
diff --git a/app/services/application_settings/base_service.rb b/app/services/application_settings/base_service.rb
new file mode 100644
index 00000000000..2bcc7d7c08b
--- /dev/null
+++ b/app/services/application_settings/base_service.rb
@@ -0,0 +1,7 @@
+module ApplicationSettings
+ class BaseService < ::BaseService
+ def initialize(application_setting, user, params = {})
+ @application_setting, @current_user, @params = application_setting, user, params.dup
+ end
+ end
+end
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb
new file mode 100644
index 00000000000..61589a07250
--- /dev/null
+++ b/app/services/application_settings/update_service.rb
@@ -0,0 +1,7 @@
+module ApplicationSettings
+ class UpdateService < ApplicationSettings::BaseService
+ def execute
+ @application_setting.update(@params)
+ end
+ end
+end
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 74b5ebf372b..6f03bf2be13 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -2,48 +2,72 @@ module Ci
# This class responsible for assigning
# proper pending build to runner on runner API request
class RegisterBuildService
- def execute(current_runner)
- builds = Ci::Build.pending.unstarted
+ include Gitlab::CurrentSettings
+ attr_reader :runner
+
+ Result = Struct.new(:build, :valid?)
+
+ def initialize(runner)
+ @runner = runner
+ end
+
+ def execute
builds =
- if current_runner.shared?
- builds.
- # don't run projects which have not enabled shared runners and builds
- joins(:project).where(projects: { shared_runners_enabled: true }).
- joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
-
- # this returns builds that are ordered by number of running builds
- # we prefer projects that don't use shared runners at all
- joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
- where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
- order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
+ if runner.shared?
+ builds_for_shared_runner
else
- # do run projects which are only assigned to this runner (FIFO)
- builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
+ builds_for_specific_runner
end
build = builds.find do |build|
- current_runner.can_pick?(build)
+ runner.can_pick?(build)
end
if build
# In case when 2 runners try to assign the same build, second runner will be declined
# with StateMachines::InvalidTransition or StaleObjectError when doing run! or save method.
- build.runner_id = current_runner.id
+ build.runner_id = runner.id
build.run!
end
- build
+ Result.new(build, true)
rescue StateMachines::InvalidTransition, ActiveRecord::StaleObjectError
- nil
+ Result.new(build, false)
end
private
+ def builds_for_shared_runner
+ new_builds.
+ # don't run projects which have not enabled shared runners and builds
+ joins(:project).where(projects: { shared_runners_enabled: true }).
+ joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
+ where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
+
+ # Implement fair scheduling
+ # this returns builds that are ordered by number of running builds
+ # we prefer projects that don't use shared runners at all
+ joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
+ order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
+ end
+
+ def builds_for_specific_runner
+ new_builds.where(project: runner.projects.with_builds_enabled).order('created_at ASC')
+ end
+
def running_builds_for_shared_runners
Ci::Build.running.where(runner: Ci::Runner.shared).
group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds')
end
+
+ def new_builds
+ Ci::Build.pending.unstarted
+ end
+
+ def shared_runner_build_limits_feature_enabled?
+ ENV['DISABLE_SHARED_RUNNER_BUILD_MINUTES_LIMIT'].to_s != 'true'
+ end
end
end
diff --git a/app/services/ci/update_build_queue_service.rb b/app/services/ci/update_build_queue_service.rb
new file mode 100644
index 00000000000..152c8ae5006
--- /dev/null
+++ b/app/services/ci/update_build_queue_service.rb
@@ -0,0 +1,19 @@
+module Ci
+ class UpdateBuildQueueService
+ def execute(build)
+ build.project.runners.each do |runner|
+ if runner.can_pick?(build)
+ runner.tick_runner_queue
+ end
+ end
+
+ return unless build.project.shared_runners_enabled?
+
+ Ci::Runner.shared.each do |runner|
+ if runner.can_pick?(build)
+ runner.tick_runner_queue
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 4ce5fd993d9..5f3ced49665 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -36,6 +36,14 @@ class IssuableBaseService < BaseService
end
end
+ def create_time_estimate_note(issuable)
+ SystemNoteService.change_time_estimate(issuable, issuable.project, current_user)
+ end
+
+ def create_time_spent_note(issuable)
+ SystemNoteService.change_time_spent(issuable, issuable.project, current_user)
+ end
+
def filter_params(issuable)
ability_name = :"admin_#{issuable.to_ability_name}"
@@ -272,6 +280,14 @@ class IssuableBaseService < BaseService
create_task_status_note(issuable)
end
+ if issuable.previous_changes.include?('time_estimate')
+ create_time_estimate_note(issuable)
+ end
+
+ if issuable.time_spent?
+ create_time_spent_note(issuable)
+ end
+
create_labels_note(issuable, old_labels) if issuable.labels != old_labels
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 70e25956dc7..5a53b973059 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -38,15 +38,13 @@ module MergeRequests
private
- def merge_requests_for(branch)
- origin_merge_requests = @project.origin_merge_requests
- .opened.where(source_branch: branch).to_a
-
- fork_merge_requests = @project.fork_merge_requests
- .opened.where(source_branch: branch).to_a
-
- (origin_merge_requests + fork_merge_requests)
- .uniq.select(&:source_project)
+ # Returns all origin and fork merge requests from `@project` satisfying passed arguments.
+ def merge_requests_for(source_branch, mr_states: [:opened])
+ MergeRequest
+ .with_state(mr_states)
+ .where(source_branch: source_branch, source_project_id: @project.id)
+ .preload(:source_project) # we don't need a #includes since we're just preloading for the #select
+ .select(&:source_project)
end
def pipeline_merge_requests(pipeline)
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index 0a9563ed7e7..b4bfb0e5e8c 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -21,6 +21,7 @@ module MergeRequests
end
comment_mr_with_commits
+ mark_mr_as_wip_from_commits
execute_mr_web_hooks
true
@@ -41,7 +42,7 @@ module MergeRequests
commit_ids.include?(merge_request.diff_head_sha)
end
- merge_requests.uniq.select(&:source_project).each do |merge_request|
+ filter_merge_requests(merge_requests).each do |merge_request|
MergeRequests::PostMergeService.
new(merge_request.target_project, @current_user).
execute(merge_request)
@@ -57,10 +58,13 @@ module MergeRequests
def reload_merge_requests
merge_requests = @project.merge_requests.opened.
by_source_or_target_branch(@branch_name).to_a
- merge_requests += fork_merge_requests
- merge_requests = filter_merge_requests(merge_requests)
- merge_requests.each do |merge_request|
+ # Fork merge requests
+ merge_requests += MergeRequest.opened
+ .where(source_branch: @branch_name, source_project: @project)
+ .where.not(target_project: @project).to_a
+
+ filter_merge_requests(merge_requests).each do |merge_request|
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_diff
else
@@ -136,6 +140,24 @@ module MergeRequests
end
end
+ def mark_mr_as_wip_from_commits
+ return unless @commits.present?
+
+ merge_requests_for_source_branch.each do |merge_request|
+ wip_commit = @commits.detect(&:work_in_progress?)
+
+ if wip_commit && !merge_request.work_in_progress?
+ merge_request.update(title: merge_request.wip_title)
+ SystemNoteService.add_merge_request_wip_from_commit(
+ merge_request,
+ merge_request.project,
+ @current_user,
+ wip_commit
+ )
+ end
+ end
+ end
+
# Call merge request webhook with update branches
def execute_mr_web_hooks
merge_requests_for_source_branch.each do |merge_request|
@@ -156,16 +178,7 @@ module MergeRequests
end
def merge_requests_for_source_branch
- @source_merge_requests ||= begin
- merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
- merge_requests += fork_merge_requests
- filter_merge_requests(merge_requests)
- end
- end
-
- def fork_merge_requests
- @fork_merge_requests ||= @project.fork_merge_requests.opened.
- where(source_branch: @branch_name).to_a
+ @source_merge_requests ||= merge_requests_for(@branch_name)
end
def branch_added?
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index ad16ef8c70f..3cb9aae83f6 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -7,6 +7,8 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
+ merge_from_slash_command(merge_request) if params[:merge]
+
if merge_request.closed_without_fork?
params.except!(:target_branch, :force_remove_source_branch)
end
@@ -69,6 +71,19 @@ module MergeRequests
end
end
+ def merge_from_slash_command(merge_request)
+ last_diff_sha = params.delete(:merge)
+ return unless merge_request.mergeable_with_slash_command?(current_user, last_diff_sha: last_diff_sha)
+
+ merge_request.update(merge_error: nil)
+
+ if merge_request.head_pipeline && merge_request.head_pipeline.active?
+ MergeRequests::MergeWhenPipelineSucceedsService.new(project, current_user).execute(merge_request)
+ else
+ MergeWorker.perform_async(merge_request.id, current_user.id, {})
+ end
+ end
+
def reopen_service
MergeRequests::ReopenService
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 1beca9f4109..b4f8b33d564 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -1,9 +1,12 @@
module Notes
class CreateService < BaseService
def execute
- note = project.notes.new(params)
- note.author = current_user
- note.system = false
+ merge_request_diff_head_sha = params.delete(:merge_request_diff_head_sha)
+
+ note = Note.new(params)
+ note.project = project
+ note.author = current_user
+ note.system = false
if note.award_emoji?
noteable = note.noteable
@@ -19,7 +22,8 @@ module Notes
slash_commands_service = SlashCommandsService.new(project, current_user)
if slash_commands_service.supported?(note)
- content, command_params = slash_commands_service.extract_commands(note)
+ options = { merge_request_diff_head_sha: merge_request_diff_head_sha }
+ content, command_params = slash_commands_service.extract_commands(note, options)
only_commands = content.empty?
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index e4cd3fc7833..6a10e172483 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -10,6 +10,9 @@ module Notes
# Skip system notes, like status changes and cross-references and awards
unless @note.system?
EventCreateService.new.leave_note(@note, @note.author)
+
+ return if @note.for_personal_snippet?
+
@note.create_cross_references!
execute_note_hooks
end
diff --git a/app/services/notes/slash_commands_service.rb b/app/services/notes/slash_commands_service.rb
index 2edbd39a9e7..56913568cae 100644
--- a/app/services/notes/slash_commands_service.rb
+++ b/app/services/notes/slash_commands_service.rb
@@ -12,17 +12,17 @@ module Notes
def self.supported?(note, current_user)
noteable_update_service(note) &&
current_user &&
- current_user.can?(:"update_#{note.noteable_type.underscore}", note.noteable)
+ current_user.can?(:"update_#{note.to_ability_name}", note.noteable)
end
def supported?(note)
self.class.supported?(note, current_user)
end
- def extract_commands(note)
+ def extract_commands(note, options = {})
return [note.note, {}] unless supported?(note)
- SlashCommands::InterpretService.new(project, current_user).
+ SlashCommands::InterpretService.new(project, current_user, options).
execute(note.note, note.noteable)
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 9a7af5730d2..f74e6cac174 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -178,8 +178,15 @@ class NotificationService
recipients = []
mentioned_users = note.mentioned_users
+
+ ability, subject = if note.for_personal_snippet?
+ [:read_personal_snippet, note.noteable]
+ else
+ [:read_project, note.project]
+ end
+
mentioned_users.select! do |user|
- user.can?(:read_project, note.project)
+ user.can?(ability, subject)
end
# Add all users participating in the thread (author, assignee, comment authors)
@@ -192,11 +199,13 @@ class NotificationService
recipients = recipients.concat(participants)
- # Merge project watchers
- recipients = add_project_watchers(recipients, note.project)
+ unless note.for_personal_snippet?
+ # Merge project watchers
+ recipients = add_project_watchers(recipients, note.project)
- # Merge project with custom notification
- recipients = add_custom_notifications(recipients, note.project, :new_note)
+ # Merge project with custom notification
+ recipients = add_custom_notifications(recipients, note.project, :new_note)
+ end
# Reject users with Mention notification level, except those mentioned in _this_ note.
recipients = reject_mention_users(recipients - mentioned_users, note.project)
@@ -211,8 +220,7 @@ class NotificationService
recipients.delete(note.author)
recipients = recipients.uniq
- # build notify method like 'note_commit_email'
- notify_method = "note_#{note.noteable_type.underscore}_email".to_sym
+ notify_method = "note_#{note.to_ability_name}_email".to_sym
recipients.each do |recipient|
mailer.send(notify_method, recipient.id, note.id).deliver_later
@@ -591,7 +599,10 @@ class NotificationService
custom_action = build_custom_key(action, target)
recipients = target.participants(current_user)
- recipients = add_project_watchers(recipients, project)
+
+ unless NotificationSetting::EXCLUDED_WATCHER_EVENTS.include?(custom_action)
+ recipients = add_project_watchers(recipients, project)
+ end
recipients = add_custom_notifications(recipients, project, custom_action)
recipients = reject_mention_users(recipients, project)
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 159f46cd465..c7cce0c55b9 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -22,17 +22,7 @@ module Projects
return @project
end
- # Set project name from path
- if @project.name.present? && @project.path.present?
- # if both name and path set - everything is ok
- elsif @project.path.present?
- # Set project name from path
- @project.name = @project.path.dup
- elsif @project.name.present?
- # For compatibility - set path from name
- # TODO: remove this in 8.0
- @project.path = @project.name.dup.parameterize
- end
+ set_project_name_from_path
# get namespace id
namespace_id = params[:namespace_id]
@@ -144,5 +134,19 @@ module Projects
service.save!
end
end
+
+ def set_project_name_from_path
+ # Set project name from path
+ if @project.name.present? && @project.path.present?
+ # if both name and path set - everything is ok
+ elsif @project.path.present?
+ # Set project name from path
+ @project.name = @project.path.dup
+ elsif @project.name.present?
+ # For compatibility - set path from name
+ # TODO: remove this in 8.0
+ @project.path = @project.name.dup.parameterize
+ end
+ end
end
end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 6040391fd94..96c363c8d1a 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -36,7 +36,7 @@ module Projects
def groups
current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
- { username: group.path, name: group.name, count: count, avatar_url: group.avatar.url }
+ { username: group.path, name: group.name, count: count, avatar_url: group.avatar_url }
end
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 8a6af8d8ada..55d9cb13ae4 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -9,7 +9,7 @@ module Projects
Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(project, new_visibility)
- return project
+ return error('Visibility level unallowed')
end
end
@@ -22,7 +22,13 @@ module Projects
if project.update_attributes(params.except(:default_branch))
if project.previous_changes.include?('path')
project.rename_repo
+ else
+ system_hook_service.execute_hooks_for(project, :update)
end
+
+ success
+ else
+ error('Project could not be updated')
end
end
end
diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb
index d75c5b1800e..3566a8ba92f 100644
--- a/app/services/slash_commands/interpret_service.rb
+++ b/app/services/slash_commands/interpret_service.rb
@@ -2,7 +2,7 @@ module SlashCommands
class InterpretService < BaseService
include Gitlab::SlashCommands::Dsl
- attr_reader :issuable
+ attr_reader :issuable, :options
# Takes a text and interprets the commands that are extracted from it.
# Returns the content without commands, and hash of changes to be applied to a record.
@@ -13,7 +13,8 @@ module SlashCommands
opts = {
issuable: issuable,
current_user: current_user,
- project: project
+ project: project,
+ params: params
}
content, commands = extractor.extract_commands(content, opts)
@@ -58,6 +59,17 @@ module SlashCommands
@updates[:state_event] = 'reopen'
end
+ desc 'Merge (when build succeeds)'
+ condition do
+ last_diff_sha = params && params[:merge_request_diff_head_sha]
+ issuable.is_a?(MergeRequest) &&
+ issuable.persisted? &&
+ issuable.mergeable_with_slash_command?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha)
+ end
+ command :merge do
+ @updates[:merge] = params[:merge_request_diff_head_sha]
+ end
+
desc 'Change title'
params '<New title>'
condition do
@@ -243,6 +255,50 @@ module SlashCommands
@updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip'
end
+ desc 'Set time estimate'
+ params '<1w 3d 2h 14m>'
+ condition do
+ current_user.can?(:"admin_#{issuable.to_ability_name}", project)
+ end
+ command :estimate do |raw_duration|
+ time_estimate = Gitlab::TimeTrackingFormatter.parse(raw_duration)
+
+ if time_estimate
+ @updates[:time_estimate] = time_estimate
+ end
+ end
+
+ desc 'Add or substract spent time'
+ params '<1h 30m | -1h 30m>'
+ condition do
+ current_user.can?(:"admin_#{issuable.to_ability_name}", issuable)
+ end
+ command :spend do |raw_duration|
+ time_spent = Gitlab::TimeTrackingFormatter.parse(raw_duration)
+
+ if time_spent
+ @updates[:spend_time] = { duration: time_spent, user: current_user }
+ end
+ end
+
+ desc 'Remove time estimate'
+ condition do
+ issuable.persisted? &&
+ current_user.can?(:"admin_#{issuable.to_ability_name}", project)
+ end
+ command :remove_estimate do
+ @updates[:time_estimate] = 0
+ end
+
+ desc 'Remove spent time'
+ condition do
+ issuable.persisted? &&
+ current_user.can?(:"admin_#{issuable.to_ability_name}", project)
+ end
+ command :remove_time_spent do
+ @updates[:spend_time] = { duration: :reset, user: current_user }
+ end
+
# This is a dummy command, so that it appears in the autocomplete commands
desc 'CC'
params '@user'
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 7613ecd5021..a11bca00687 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -109,6 +109,57 @@ module SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
+ # Called when the estimated time of a Noteable is changed
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # time_estimate - Estimated time
+ #
+ # Example Note text:
+ #
+ # "Changed estimate of this issue to 3d 5h"
+ #
+ # Returns the created Note object
+
+ def change_time_estimate(noteable, project, author)
+ parsed_time = Gitlab::TimeTrackingFormatter.output(noteable.time_estimate)
+ body = if noteable.time_estimate == 0
+ "Removed time estimate on this #{noteable.human_class_name}"
+ else
+ "Changed time estimate of this #{noteable.human_class_name} to #{parsed_time}"
+ end
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
+ # Called when the spent time of a Noteable is changed
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # time_spent - Spent time
+ #
+ # Example Note text:
+ #
+ # "Added 2h 30m of time spent on this issue"
+ #
+ # Returns the created Note object
+
+ def change_time_spent(noteable, project, author)
+ time_spent = noteable.time_spent
+
+ if time_spent == :reset
+ body = "Removed time spent on this #{noteable.human_class_name}"
+ else
+ parsed_time = Gitlab::TimeTrackingFormatter.output(time_spent.abs)
+ action = time_spent > 0 ? 'Added' : 'Subtracted'
+ body = "#{action} #{parsed_time} of time spent on this #{noteable.human_class_name}"
+ end
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
# Called when the status of a Noteable is changed
#
# noteable - Noteable object
@@ -157,6 +208,12 @@ module SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
+ def add_merge_request_wip_from_commit(noteable, project, author, commit)
+ body = "marked as a **Work In Progress** from #{commit.to_reference(project)}"
+
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
def self.resolve_all_discussions(merge_request, project, author)
body = "resolved all discussions"
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index f8e6b2ef094..1bd6ce416ab 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -98,10 +98,12 @@ class TodoService
# When a build fails on the HEAD of a merge request we should:
#
- # * create a todo for that user to fix it
+ # * create a todo for author of MR to fix it
+ # * create a todo for merge_user to keep an eye on it
#
def merge_request_build_failed(merge_request)
- create_build_failed_todo(merge_request)
+ create_build_failed_todo(merge_request, merge_request.author)
+ create_build_failed_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_build_succeeds?
end
# When a new commit is pushed to a merge request we should:
@@ -115,11 +117,21 @@ class TodoService
# When a build is retried to a merge request we should:
#
# * mark all pending todos related to the merge request for the author as done
+ # * mark all pending todos related to the merge request for the merge_user as done
#
def merge_request_build_retried(merge_request)
mark_pending_todos_as_done(merge_request, merge_request.author)
+ mark_pending_todos_as_done(merge_request, merge_request.merge_user) if merge_request.merge_when_build_succeeds?
end
-
+
+ # When a merge request could not be automatically merged due to its unmergeable state we should:
+ #
+ # * create a todo for a merge_user
+ #
+ def merge_request_became_unmergeable(merge_request)
+ create_unmergeable_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_build_succeeds?
+ end
+
# When create a note we should:
#
# * mark all pending todos related to the noteable for the note author as done
@@ -236,10 +248,14 @@ class TodoService
create_todos(mentioned_users, attributes)
end
- def create_build_failed_todo(merge_request)
- author = merge_request.author
- attributes = attributes_for_todo(merge_request.project, merge_request, author, Todo::BUILD_FAILED)
- create_todos(author, attributes)
+ def create_build_failed_todo(merge_request, todo_author)
+ attributes = attributes_for_todo(merge_request.project, merge_request, todo_author, Todo::BUILD_FAILED)
+ create_todos(todo_author, attributes)
+ end
+
+ def create_unmergeable_todo(merge_request, merge_user)
+ attributes = attributes_for_todo(merge_request.project, merge_request, merge_user, Todo::UNMERGEABLE)
+ create_todos(merge_user, attributes)
end
def attributes_for_target(target)
diff --git a/app/services/user_project_access_changed_service.rb b/app/services/user_project_access_changed_service.rb
index 2469b4f0d7c..d7a6804ee88 100644
--- a/app/services/user_project_access_changed_service.rb
+++ b/app/services/user_project_access_changed_service.rb
@@ -4,6 +4,6 @@ class UserProjectAccessChangedService
end
def execute
- AuthorizedProjectsWorker.bulk_perform_async(@user_ids.map { |id| [id] })
+ AuthorizedProjectsWorker.bulk_perform_and_wait(@user_ids.map { |id| [id] })
end
end
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 8559908e0c3..fad741531ea 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -26,8 +26,26 @@ module Users
user.reload
end
- # This method returns the updated User object.
def execute
+ lease_key = "refresh_authorized_projects:#{user.id}"
+ lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
+
+ until uuid = lease.try_obtain
+ # Keep trying until we obtain the lease. If we don't do so we may end up
+ # not updating the list of authorized projects properly. To prevent
+ # hammering Redis too much we'll wait for a bit between retries.
+ sleep(1)
+ end
+
+ begin
+ execute_without_lease
+ ensure
+ Gitlab::ExclusiveLease.cancel(lease_key, uuid)
+ end
+ end
+
+ # This method returns the updated User object.
+ def execute_without_lease
current = current_authorizations_per_project
fresh = fresh_access_levels_per_project
@@ -35,7 +53,7 @@ module Users
# rows not in the new list or with a different access level should be
# removed.
if !fresh[project_id] || fresh[project_id] != row.access_level
- array << row.id
+ array << row.project_id
end
end
@@ -47,26 +65,7 @@ module Users
end
end
- update_with_lease(remove, add)
- end
-
- # Updates the list of authorizations using an exclusive lease.
- def update_with_lease(remove = [], add = [])
- lease_key = "refresh_authorized_projects:#{user.id}"
- lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
-
- until uuid = lease.try_obtain
- # Keep trying until we obtain the lease. If we don't do so we may end up
- # not updating the list of authorized projects properly. To prevent
- # hammering Redis too much we'll wait for a bit between retries.
- sleep(1)
- end
-
- begin
- update_authorizations(remove, add)
- ensure
- Gitlab::ExclusiveLease.cancel(lease_key, uuid)
- end
+ update_authorizations(remove, add)
end
# Updates the list of authorizations for the current user.
@@ -100,7 +99,7 @@ module Users
end
def current_authorizations
- user.project_authorizations.select(:id, :project_id, :access_level)
+ user.project_authorizations.select(:project_id, :access_level)
end
def fresh_authorizations
@@ -119,7 +118,8 @@ module Users
user.personal_projects.select("#{user.id} AS user_id, projects.id AS project_id, #{Gitlab::Access::MASTER} AS access_level"),
user.groups_projects.select_for_project_authorization,
user.projects.select_for_project_authorization,
- user.groups.joins(:shared_projects).select_for_project_authorization
+ user.groups.joins(:shared_projects).select_for_project_authorization,
+ user.nested_projects.select_for_project_authorization
]
Gitlab::SQL::Union.new(relations)
diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb
index a1ecb7bc00b..265cea2d2c6 100644
--- a/app/uploaders/avatar_uploader.rb
+++ b/app/uploaders/avatar_uploader.rb
@@ -10,4 +10,15 @@ class AvatarUploader < GitlabUploader
def exists?
model.avatar.file && model.avatar.file.exists?
end
+
+ # We set move_to_store and move_to_cache to 'false' to prevent stealing
+ # the avatar file from a project when forking it.
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/26158
+ def move_to_store
+ false
+ end
+
+ def move_to_cache
+ false
+ end
end
diff --git a/app/validators/project_path_validator.rb b/app/validators/project_path_validator.rb
index aec0d0ce44e..36279daa743 100644
--- a/app/validators/project_path_validator.rb
+++ b/app/validators/project_path_validator.rb
@@ -15,7 +15,7 @@ class ProjectPathValidator < ActiveModel::EachValidator
# 'tree' as project name and 'deploy_keys' as route.
#
RESERVED = (NamespaceValidator::RESERVED -
- %w[dashboard help ci admin search notes services] +
+ %w[dashboard help ci admin search notes services assets profile public] +
%w[tree commits wikis new edit create update logs_tree
preview blob blame raw files create_dir find_file]).freeze
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 4612a7a058a..558bbe07b16 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -421,6 +421,23 @@
= link_to "Koding administration documentation", help_page_path("administration/integration/koding")
%fieldset
+ %legend PlantUML
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :plantuml_enabled do
+ = f.check_box :plantuml_enabled
+ Enable PlantUML
+ .form-group
+ = f.label :plantuml_url, 'PlantUML URL', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080'
+ .help-block
+ Allow rendering of
+ = link_to "PlantUML", "http://plantuml.com"
+ diagrams in Asciidoc documents using an external PlantUML service.
+
+ %fieldset
%legend Usage statistics
.form-group
.col-sm-offset-2.col-sm-10
diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index 3132d157f29..2269fb1fd8c 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -4,7 +4,7 @@
- if @broadcast_message.message.present?
= render_broadcast_message(@broadcast_message)
- else
- = "Your message here"
+ Your message here
= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f|
= form_errors(@broadcast_message)
diff --git a/app/views/admin/identities/_identity.html.haml b/app/views/admin/identities/_identity.html.haml
index 7362d904b94..8c658905bd6 100644
--- a/app/views/admin/identities/_identity.html.haml
+++ b/app/views/admin/identities/_identity.html.haml
@@ -1,6 +1,6 @@
%tr
%td
- = "#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})"
+ #{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})
%td
= identity.extern_uid
%td
diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml
index 37bb6a3b0e0..124f970524e 100644
--- a/app/views/admin/runners/index.html.haml
+++ b/app/views/admin/runners/index.html.haml
@@ -11,7 +11,7 @@
that for future communication.
%br
Registration token is
- %code{ id: 'runners-token' } #{current_application_settings.runners_registration_token}
+ %code#runners-token= current_application_settings.runners_registration_token
.bs-callout.clearfix
.pull-left
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index ca503e35623..39e103e3062 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -100,7 +100,7 @@
%td.build-link
- if project
= link_to ci_status_path(build.pipeline) do
- %strong #{build.pipeline.short_sha}
+ %strong= build.pipeline.short_sha
%td.timestamp
- if build.finished_at
diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml
index bfc6142067a..2e5f120c4e4 100644
--- a/app/views/admin/system_info/show.html.haml
+++ b/app/views/admin/system_info/show.html.haml
@@ -10,7 +10,7 @@
%h4 CPU
.data
- if @cpus
- %h1= "#{@cpus.length} cores"
+ %h1 #{@cpus.length} cores
- else
= icon('warning', class: 'text-warning')
Unable to collect CPU info
@@ -19,7 +19,7 @@
%h4 Memory
.data
- if @memory
- %h1= "#{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}"
+ %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)}
- else
= icon('warning', class: 'text-warning')
Unable to collect memory info
@@ -28,6 +28,6 @@
%h4 Disks
.data
- @disks.each do |disk|
- %h1= "#{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}"
- %p= "#{disk[:disk_name]}"
- %p= "#{disk[:mount_path]}"
+ %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])}
+ %p= disk[:disk_name]
+ %p= disk[:mount_path]
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index a71240986c9..76b1291fe10 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -186,6 +186,6 @@
- if @user.solo_owned_groups.present?
%p
This user is currently an owner in these groups:
- %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
+ %strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
You must transfer ownership or delete these groups before you can delete this user.
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index d8912eda314..e3305e21e96 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -2,8 +2,7 @@
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
- disabled: !current_user,
- class: (award_active_class(awards, current_user)),
+ class: (award_state_class(awards, current_user)),
data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji, sprite: false)
%span.award-control-text.js-counter
diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml
index 889086c62b1..b0bee1c6204 100644
--- a/app/views/ci/lints/show.html.haml
+++ b/app/views/ci/lints/show.html.haml
@@ -1,20 +1,25 @@
- page_title "CI Lint"
- page_description "Validate your GitLab CI configuration file"
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/ace.js')
%h2 Check your .gitlab-ci.yml
-%hr
-.row
- = form_tag ci_lint_path, method: :post do
- .form-group
- = label_tag(:content, 'Content of .gitlab-ci.yml', class: 'control-label text-nowrap')
+.ci-linter
+ .row
+ = form_tag ci_lint_path, method: :post do
+ .form-group
+ .col-sm-12
+ .file-holder
+ .file-title.clearfix
+ Content of .gitlab-ci.yml
+ #ci-editor.ci-editor= @content
+ = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true)
.col-sm-12
- = text_area_tag(:content, @content, class: 'form-control span1', rows: 7, require: true)
- .col-sm-12
- .pull-left.prepend-top-10
- = submit_tag('Validate', class: 'btn btn-success submit-yml')
+ .pull-left.prepend-top-10
+ = submit_tag('Validate', class: 'btn btn-success submit-yml')
-.row.prepend-top-20
- .col-sm-12
- .results
- = render partial: 'create' if defined?(@status)
+ .row.prepend-top-20
+ .col-sm-12
+ .results.ci-template
+ = render partial: 'create' if defined?(@status)
diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml
index 601fb7f0f3f..c00c7f7407e 100644
--- a/app/views/ci/status/_badge.html.haml
+++ b/app/views/ci/status/_badge.html.haml
@@ -1,7 +1,8 @@
- status = local_assigns.fetch(:status)
+- link = local_assigns.fetch(:link, true)
- css_classes = "ci-status ci-#{status.group}"
-- if status.has_details?
+- if link && status.has_details?
= link_to status.details_path, class: css_classes do
= custom_icon(status.icon)
= status.text
diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml
new file mode 100644
index 00000000000..8dea3479f82
--- /dev/null
+++ b/app/views/ci/status/_dropdown_graph_badge.html.haml
@@ -0,0 +1,19 @@
+-# Renders the content of each li in the dropdown
+
+- subject = local_assigns.fetch(:subject)
+- status = subject.detailed_status(current_user)
+- klass = "ci-status-icon ci-status-icon-#{status.group}"
+- tooltip = "#{subject.name} - #{status.label}"
+
+- if status.has_details?
+ = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip } do
+ %span{ class: klass }= custom_icon(status.icon)
+ %span.ci-build-text= subject.name
+- else
+ .mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', title: tooltip } }
+ %span{ class: klass }= custom_icon(status.icon)
+ %span.ci-build-text= subject.name
+
+- if status.has_action?
+ = link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do
+ = icon(status.action_icon, class: status.action_class)
diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml
index 9849b31d7e2..9d7bcdb9d16 100644
--- a/app/views/dashboard/todos/_todo.html.haml
+++ b/app/views/dashboard/todos/_todo.html.haml
@@ -3,7 +3,7 @@
.todo-item.todo-block
.todo-title.title
- - unless todo.build_failed?
+ - unless todo.build_failed? || todo.unmergeable?
= todo_target_state_pill(todo)
%span.author-name
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 545a938f4be..01ecf237925 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -15,6 +15,9 @@
.form-group
= f.label :email
= f.email_field :email, class: "form-control middle", required: true, title: "Please provide a valid email address."
+ .form-group
+ = f.label :email_confirmation
+ = f.email_field :email_confirmation, class: "form-control middle", required: true, title: "Please retype the email address."
.form-group.append-bottom-20#password-strength
= f.label :password
= f.password_field :password, class: "form-control bottom", required: true, pattern: ".{#{@minimum_password_length},}", title: "Minimum length is #{@minimum_password_length} characters."
diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml
index 2bce2780484..6f5d4bf2a2f 100644
--- a/app/views/discussions/_discussion.html.haml
+++ b/app/views/discussions/_discussion.html.haml
@@ -1,6 +1,9 @@
- expanded = discussion.expanded?
%li.note.note-discussion.timeline-entry
.timeline-entry-inner
+ .timeline-icon
+ = link_to user_path(discussion.author) do
+ = image_tag avatar_icon(discussion.author), class: "avatar s40"
.timeline-content
.discussion.js-toggle-container{ class: discussion.id, data: { discussion_id: discussion.id } }
.discussion-header
diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml
index ef16b516e2c..3a19e021643 100644
--- a/app/views/discussions/_parallel_diff_discussion.html.haml
+++ b/app/views/discussions/_parallel_diff_discussion.html.haml
@@ -6,7 +6,7 @@
.content{ class: ('hide' unless discussion_left.expanded?) }
= render "discussions/notes", discussion: discussion_left, line_type: 'old'
- else
- %td.notes_line.old= ""
+ %td.notes_line.old= ("")
%td.notes_content.parallel.old
.content
@@ -16,6 +16,6 @@
.content{ class: ('hide' unless discussion_right.expanded?) }
= render "discussions/notes", discussion: discussion_right, line_type: 'new'
- else
- %td.notes_line.new= ""
+ %td.notes_line.new= ("")
%td.notes_content.parallel.new
.content
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index 2a0e301c8dd..a196561f381 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -10,7 +10,7 @@
%p
= icon("exclamation-triangle fw")
You are an admin, which means granting access to
- %strong #{@pre_auth.client.name}
+ %strong= @pre_auth.client.name
will allow them to interact with GitLab as an admin as well. Proceed with caution.
- if @pre_auth.scopes
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index bc5d3c797ac..f4c432a095a 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -25,7 +25,7 @@
.panel.panel-default
.panel-heading
Users with access to
- %strong #{@group.name}
+ %strong= @group.name
%span.badge= @members.total_count
%ul.content-list
= render partial: 'shared/members/member', collection: @members, as: :member
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index b4aa4f24d9e..6ad03a60b3a 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -18,7 +18,7 @@
.row-content-block.second-block
Only issues from the
- %strong #{@group.name}
+ %strong= @group.name
group are listed here.
- if current_user
To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page.
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index dbbdb583a24..af73554086b 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -10,7 +10,7 @@
.row-content-block.second-block
Only merge requests from
- %strong #{@group.name}
+ %strong= @group.name
group are listed here.
- if current_user
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml
index a8fdbd8c426..cd5388fe402 100644
--- a/app/views/groups/milestones/index.html.haml
+++ b/app/views/groups/milestones/index.html.haml
@@ -10,7 +10,7 @@
.row-content-block
Only milestones from
- %strong #{@group.name}
+ %strong= @group.name
group are listed here.
.milestones
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index d19eaa6add9..38d63fd9acc 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -21,5 +21,5 @@
= render 'shared/group_tips'
.form-actions
- = f.submit 'Create group', class: "btn btn-create", tabindex: 3
+ = f.submit 'Create group', class: "btn btn-create"
= link_to 'Cancel', dashboard_groups_path, class: 'btn btn-cancel'
diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml
index 864c5c0ff95..0e7f0b5ed4f 100644
--- a/app/views/import/_githubish_status.html.haml
+++ b/app/views/import/_githubish_status.html.haml
@@ -16,7 +16,7 @@
%colgroup.import-jobs-status-col
%thead
%tr
- %th= "From #{provider_title}"
+ %th From #{provider_title}
%th To GitLab
%th Status
%tbody
diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml
index 07338736bac..9999a4362c6 100644
--- a/app/views/import/fogbugz/new_user_map.html.haml
+++ b/app/views/import/fogbugz/new_user_map.html.haml
@@ -37,7 +37,7 @@
%tbody
- @user_map.each do |id, user|
%tr
- %td= id
+ %td= (id)
%td= text_field_tag "users[#{id}][name]", user[:name], class: 'form-control'
%td= text_field_tag "users[#{id}][email]", user[:email], class: 'form-control'
%td
diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml
index 97e5e51abe0..5de5da5e6a2 100644
--- a/app/views/import/fogbugz/status.html.haml
+++ b/app/views/import/fogbugz/status.html.haml
@@ -50,7 +50,7 @@
%td
= repo.name
%td.import-target
- = "#{current_user.username}/#{repo.name}"
+ #{current_user.username}/#{repo.name}
%td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do
Import
diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml
index 9f1507cade6..5e01af008be 100644
--- a/app/views/import/google_code/status.html.haml
+++ b/app/views/import/google_code/status.html.haml
@@ -55,7 +55,7 @@
%td
= link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank"
%td.import-target
- = "#{current_user.username}/#{repo.name}"
+ #{current_user.username}/#{repo.name}
%td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do
Import
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index f4e0244596c..9ecc0d11c95 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -44,7 +44,7 @@
%li
= link_to "Profile", current_user, class: 'profile-link', aria: { label: "Profile" }, data: { user: current_user.username }
%li
- = link_to "Profile Settings", profile_path, aria: { label: "Profile Settings" }
+ = link_to "Settings", profile_path, aria: { label: "Settings" }
%li
= link_to "Help", help_path, aria: { label: "Help" }
%li.divider
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index b69114c96cc..ac04f57e217 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -31,7 +31,7 @@
= link_to admin_abuse_reports_path, title: "Abuse Reports" do
%span
Abuse Reports
- %span.badge.badge-dark.count= number_with_delimiter(AbuseReport.count(:all))
+ %span.badge.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled?
= nav_link(controller: :spam_logs) do
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 221f3ec1ffe..f3539fd372d 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -26,13 +26,13 @@
%span
Issues
- issues = IssuesFinder.new(current_user, group_id: @group.id, state: 'opened').execute
- %span.badge.badge-dark.count= number_with_delimiter(issues.count)
+ %span.badge.count= number_with_delimiter(issues.count)
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
%span
Merge Requests
- merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute
- %span.badge.badge-dark.count= number_with_delimiter(merge_requests.count)
+ %span.badge.count= number_with_delimiter(merge_requests.count)
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do
%span
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 3c8c7b8f25e..a8bbd67de80 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -61,14 +61,14 @@
%span
Issues
- if @project.default_issues_tracker?
- %span.badge.badge-dark.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
+ %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count)
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
= link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do
%span
Merge Requests
- %span.badge.badge-dark.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
+ %span.badge.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count)
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 613b8b7d301..c6df66d2c3c 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -1,26 +1,17 @@
- if project_nav_tab? :team
- = nav_link(controller: [:project_members, :teams]) do
- = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
+ = nav_link(controller: [:members, :teams]) do
+ = link_to namespace_project_settings_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab' do
%span
Members
- if can_edit
- - if @project.allowed_to_share_with_group?
- = nav_link(controller: :group_links) do
- = link_to namespace_project_group_links_path(@project.namespace, @project), title: "Groups" do
- %span
- Groups
= nav_link(controller: :deploy_keys) do
= link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do
%span
Deploy Keys
- = nav_link(controller: :hooks) do
- = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Webhooks' do
- %span
- Webhooks
- = nav_link(controller: :services) do
- = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do
+ = nav_link(controller: :integrations) do
+ = link_to namespace_project_settings_integrations_path(@project.namespace, @project), title: 'Integrations' do
%span
- Services
+ Integrations
= nav_link(controller: :protected_branches) do
= link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do
%span
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index b77d3402a2e..0ee8a57dbd4 100644
--- a/app/views/layouts/profile.html.haml
+++ b/app/views/layouts/profile.html.haml
@@ -1,5 +1,5 @@
-- page_title "Profile Settings"
-- header_title "Profile Settings", profile_path unless header_title
+- page_title "User Settings"
+- header_title "User Settings", profile_path unless header_title
- sidebar "dashboard"
- nav "profile"
diff --git a/app/views/notify/_reassigned_issuable_email.html.haml b/app/views/notify/_reassigned_issuable_email.html.haml
index 56d81b2ed2e..fd35713f79c 100644
--- a/app/views/notify/_reassigned_issuable_email.html.haml
+++ b/app/views/notify/_reassigned_issuable_email.html.haml
@@ -2,9 +2,9 @@
Assignee changed
- if @previous_assignee
from
- %strong #{@previous_assignee.name}
+ %strong= @previous_assignee.name
to
- if issuable.assignee_id
- %strong #{issuable.assignee_name}
+ %strong= issuable.assignee_name
- else
%strong Unassigned
diff --git a/app/views/notify/closed_issue_email.html.haml b/app/views/notify/closed_issue_email.html.haml
index 56c18cd83cd..b7284dd819b 100644
--- a/app/views/notify/closed_issue_email.html.haml
+++ b/app/views/notify/closed_issue_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Issue was closed by #{@updated_by.name}"
+ Issue was closed by #{@updated_by.name}
diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml
index ac703b31edd..bc12e38675f 100644
--- a/app/views/notify/closed_issue_email.text.haml
+++ b/app/views/notify/closed_issue_email.text.haml
@@ -1,3 +1,3 @@
-= "Issue was closed by #{@updated_by.name}"
+Issue was closed by #{@updated_by.name}
Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)}
diff --git a/app/views/notify/closed_merge_request_email.html.haml b/app/views/notify/closed_merge_request_email.html.haml
index 81c7c88fc96..44e018304e1 100644
--- a/app/views/notify/closed_merge_request_email.html.haml
+++ b/app/views/notify/closed_merge_request_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
+ Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml
index b435067d5a6..d0c96b83976 100644
--- a/app/views/notify/closed_merge_request_email.text.haml
+++ b/app/views/notify/closed_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}"
+Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/issue_status_changed_email.html.haml b/app/views/notify/issue_status_changed_email.html.haml
index 482c884a9db..b6051b11cea 100644
--- a/app/views/notify/issue_status_changed_email.html.haml
+++ b/app/views/notify/issue_status_changed_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Issue was #{@issue_status} by #{@updated_by.name}"
+ Issue was #{@issue_status} by #{@updated_by.name}
diff --git a/app/views/notify/merge_request_status_email.html.haml b/app/views/notify/merge_request_status_email.html.haml
index 41a320d6bd8..b487e26b122 100644
--- a/app/views/notify/merge_request_status_email.html.haml
+++ b/app/views/notify/merge_request_status_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}"
+ Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml
index 7a5074a1dc3..4c9719ba732 100644
--- a/app/views/notify/merge_request_status_email.text.haml
+++ b/app/views/notify/merge_request_status_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}"
+Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/merged_merge_request_email.html.haml b/app/views/notify/merged_merge_request_email.html.haml
index fbe506d4f4d..0fe54e73313 100644
--- a/app/views/notify/merged_merge_request_email.html.haml
+++ b/app/views/notify/merged_merge_request_email.html.haml
@@ -1,2 +1,2 @@
%p
- = "Merge Request #{@merge_request.to_reference} was merged"
+ Merge Request #{@merge_request.to_reference} was merged
diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml
index bfbae01094f..46c1c9dee0b 100644
--- a/app/views/notify/merged_merge_request_email.text.haml
+++ b/app/views/notify/merged_merge_request_email.text.haml
@@ -1,4 +1,4 @@
-= "Merge Request #{@merge_request.to_reference} was merged"
+Merge Request #{@merge_request.to_reference} was merged
Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)}
diff --git a/app/views/notify/note_personal_snippet_email.html.haml b/app/views/notify/note_personal_snippet_email.html.haml
new file mode 100644
index 00000000000..2fa2f784661
--- /dev/null
+++ b/app/views/notify/note_personal_snippet_email.html.haml
@@ -0,0 +1 @@
+= render 'note_message'
diff --git a/app/views/notify/note_personal_snippet_email.text.erb b/app/views/notify/note_personal_snippet_email.text.erb
new file mode 100644
index 00000000000..b2a8809a23b
--- /dev/null
+++ b/app/views/notify/note_personal_snippet_email.text.erb
@@ -0,0 +1,8 @@
+New comment for Snippet <%= @snippet.id %>
+
+<%= url_for(snippet_url(@snippet, anchor: "note_#{@note.id}")) %>
+
+
+Author: <%= @note.author_name %>
+
+<%= @note.note %>
diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml
index 82c7fe229b8..d9ebbaa2704 100644
--- a/app/views/notify/pipeline_failed_email.html.haml
+++ b/app/views/notify/pipeline_failed_email.html.haml
@@ -139,7 +139,7 @@
had
= failed.size
failed
- = "#{'build'.pluralize(failed.size)}."
+ #{'build'.pluralize(failed.size)}.
%tr.warning
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" }
Logs may contain sensitive data. Please consider before forwarding this email.
diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml
index 6dddb3b6373..8add2e18206 100644
--- a/app/views/notify/pipeline_success_email.html.haml
+++ b/app/views/notify/pipeline_success_email.html.haml
@@ -138,9 +138,9 @@
%a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" }
= "\##{@pipeline.id}"
successfully completed
- = "#{build_count} #{'build'.pluralize(build_count)}"
+ #{build_count} #{'build'.pluralize(build_count)}
in
- = "#{stage_count} #{'stage'.pluralize(stage_count)}."
+ #{stage_count} #{'stage'.pluralize(stage_count)}.
%tr.footer
%td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" }
%img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/
diff --git a/app/views/notify/project_was_not_exported_email.text.haml b/app/views/notify/project_was_not_exported_email.text.haml
index 27785165c2d..6c6902994a1 100644
--- a/app/views/notify/project_was_not_exported_email.text.haml
+++ b/app/views/notify/project_was_not_exported_email.text.haml
@@ -1,6 +1,6 @@
-= "Project #{@project.name} couldn't be exported."
+Project #{@project.name} couldn't be exported.
-= "The errors we encountered were:"
+The errors we encountered were:
- @errors.each do |error|
#{error}
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 36858fa6f34..c6b1db17f91 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -17,7 +17,7 @@
%ul
- @message.commits.each do |commit|
%li
- %strong #{link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))}
+ %strong= link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))
%div
%span by #{commit.author_name}
%i at #{commit.committed_date.to_s(:iso8601)}
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 72f658d1b68..14b330d16ad 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -102,7 +102,7 @@
= f.text_field :username, required: true, class: 'form-control'
.help-block
Current path:
- = "#{root_url}#{current_user.username}"
+ #{root_url}#{current_user.username}
.prepend-top-default
= f.button class: "btn btn-warning", type: "submit" do
= icon "spinner spin", class: "hidden loading-username"
@@ -128,7 +128,7 @@
- if @user.solo_owned_groups.present?
%p
Your account is currently an owner in these groups:
- %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
+ %strong= @user.solo_owned_groups.map(&:name).join(', ')
%p
You must transfer ownership or delete these groups before you can delete your account.
.append-bottom-default
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index 3276db6692c..d2a60ac2867 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -6,6 +6,9 @@
= key.title
.description
= key.fingerprint
+ .last-used-at
+ last used:
+ = key.last_used_at ? time_ago_with_tooltip(key.last_used_at) : 'n/a'
.pull-right
%span.key-created-at
created #{time_ago_with_tooltip(key.created_at)}
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index dd7615400dc..d44603c638c 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -11,6 +11,9 @@
%li
%span.light Created on:
%strong= @key.created_at.to_s(:medium)
+ %li
+ %span.light Last used on:
+ %strong= @key.last_used_at.try(:to_s, :medium) || 'N/A'
.col-md-8
%p
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index bb4effeeeb1..60a561c9f9c 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -19,7 +19,7 @@
Your New Personal Access Token
.form-group
= text_field_tag 'created-personal-access-token', flash[:personal_access_token], readonly: true, class: "form-control", 'aria-describedby' => "created-personal-access-token-help-block"
- = clipboard_button(clipboard_text: flash[:personal_access_token])
+ = clipboard_button(clipboard_text: flash[:personal_access_token], title: "Copy personal access token to clipboard", placement: "left")
%span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again.
%hr
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 2385a90401e..d551754a2e5 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -18,7 +18,8 @@
or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host}
.col-lg-9
.clearfix.avatar-image.append-bottom-default
- = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
+ = link_to avatar_icon(@user, 400), target: '_blank' do
+ = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160'
%h5.prepend-top-0
Upload new avatar
.prepend-top-5.append-bottom-10
@@ -61,7 +62,7 @@
%span.help-block
Please click the link in the confirmation email before continuing. It was sent to
= succeed "." do
- %strong #{@user.unconfirmed_email}
+ %strong= @user.unconfirmed_email
%p
= link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index 085f79de785..23e27c1105c 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -23,7 +23,7 @@
= markdown_toolbar_button({ icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" })
= markdown_toolbar_button({ icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" })
.toolbar-group
- %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
+ %button.toolbar-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } }
= icon("arrows-alt fw")
.md-write-holder
diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml
index afe2fd7fd7b..1a1327fb53c 100644
--- a/app/views/projects/_merge_request_merge_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_settings.html.haml
@@ -8,7 +8,7 @@
%br
%span.descr
Builds need to be configured to enable this feature.
- = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds')
+ = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds')
.checkbox
= form.label :only_allow_merge_if_all_discussions_are_resolved do
= form.check_box :only_allow_merge_if_all_discussions_are_resolved
diff --git a/app/views/projects/_visibility_select.html.haml b/app/views/projects/_visibility_select.html.haml
new file mode 100644
index 00000000000..65fc0a36ca9
--- /dev/null
+++ b/app/views/projects/_visibility_select.html.haml
@@ -0,0 +1,7 @@
+- if can_change_visibility_level?(@project, current_user)
+ = form.select(model_method, visibility_select_options(@project, selected_level), {}, class: 'form-control visibility-select')
+- else
+ .info.js-locked{ data: { help_block: visibility_level_description(@project.visibility_level, @project) } }
+ = visibility_level_icon(@project.visibility_level)
+ %strong
+ = visibility_level_label(@project.visibility_level)
diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml
index 350bdf5f836..f75f438ee4f 100644
--- a/app/views/projects/blob/_blob.html.haml
+++ b/app/views/projects/blob/_blob.html.haml
@@ -18,7 +18,7 @@
- else
= link_to title, '#'
-%ul.blob-commit-info.hidden-xs
+%ul.blob-commit-info.table-list.hidden-xs
- blob_commit = @repository.last_commit_for_path(@commit.id, blob.path)
= render blob_commit, project: @project, ref: @ref
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 1d058daa094..228ac61fc8c 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -34,7 +34,7 @@
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2'
.file-editor.code
- %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]}
+ %pre.js-edit-mode-pane#editor= params[:content] || local_assigns[:blob_data]
- if local_assigns[:path]
.js-edit-mode-pane#preview.hide
.center
diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml
index a486b2fe491..f864702d862 100644
--- a/app/views/projects/blob/_image.html.haml
+++ b/app/views/projects/blob/_image.html.haml
@@ -1,8 +1,8 @@
.file-content.image_file
- if blob.svg?
- if blob.size_within_svg_limits?
- - # We need to scrub SVG but we cannot do so in the RawController: it would
- - # be wrong/strange if RawController modified the data.
+ -# We need to scrub SVG but we cannot do so in the RawController: it would
+ -# be wrong/strange if RawController modified the data.
- blob.load_all_data!(@repository)
- blob = sanitize_svg(blob)
%img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}", alt: "#{blob.name}" }
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index 61a7ffdd0ab..4924c73cf8e 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -3,7 +3,7 @@
.modal-content
.modal-header
%a.close{ href: "#", "data-dismiss" => "modal" } ×
- %h3.page-title #{title}
+ %h3.page-title= title
.modal-body
= form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do
.dropzone
diff --git a/app/views/projects/boards/components/_sidebar.html.haml b/app/views/projects/boards/components/_sidebar.html.haml
index 2125c3387c4..df7fa9ddaf2 100644
--- a/app/views/projects/boards/components/_sidebar.html.haml
+++ b/app/views/projects/boards/components/_sidebar.html.haml
@@ -1,23 +1,24 @@
%board-sidebar{ "inline-template" => true,
":current-user" => "#{current_user ? current_user.to_json(only: [:username, :id, :name], methods: [:avatar_url]) : {}}" }
- %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" }
- .issuable-sidebar
- .block.issuable-sidebar-header
- %span.issuable-header-text.hide-collapsed.pull-left
- %strong
- {{ issue.title }}
- %br/
- %span
- = precede "#" do
- {{ issue.id }}
- %a.gutter-toggle.pull-right{ role: "button",
- href: "#",
- "@click.prevent" => "closeSidebar",
- "aria-label" => "Toggle sidebar" }
- = custom_icon("icon_close", size: 15)
- .js-issuable-update
- = render "projects/boards/components/sidebar/assignee"
- = render "projects/boards/components/sidebar/milestone"
- = render "projects/boards/components/sidebar/due_date"
- = render "projects/boards/components/sidebar/labels"
- = render "projects/boards/components/sidebar/notifications"
+ %transition{ name: "boards-sidebar-slide" }
+ %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" }
+ .issuable-sidebar
+ .block.issuable-sidebar-header
+ %span.issuable-header-text.hide-collapsed.pull-left
+ %strong
+ {{ issue.title }}
+ %br/
+ %span
+ = precede "#" do
+ {{ issue.id }}
+ %a.gutter-toggle.pull-right{ role: "button",
+ href: "#",
+ "@click.prevent" => "closeSidebar",
+ "aria-label" => "Toggle sidebar" }
+ = custom_icon("icon_close", size: 15)
+ .js-issuable-update
+ = render "projects/boards/components/sidebar/assignee"
+ = render "projects/boards/components/sidebar/milestone"
+ = render "projects/boards/components/sidebar/due_date"
+ = render "projects/boards/components/sidebar/labels"
+ = render "projects/boards/components/sidebar/notifications"
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 2eb49685f08..19ffe73a08d 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -17,9 +17,8 @@
- if @project.protected_branch? branch.name
%span.label.label-success
- %i.fa.fa-lock
protected
- .controls.hidden-xs
+ .controls.hidden-xs<
- if merge_project && create_mr_button?(@repository.root_ref, branch.name)
= link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do
Merge Request
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index ecd812312c0..bd1f2d96f56 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -5,13 +5,14 @@
%div{ class: container_class }
.top-area.adjust
.nav-text
- Protected branches can be managed in project settings
+ Protected branches can be managed in
+ = link_to 'project settings', namespace_project_protected_branches_path(@project.namespace, @project)
.nav-controls
= form_tag(filter_branches_path, method: :get) do
= search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
- .dropdown.inline
+ .dropdown.inline>
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.light
= projects_sort_options_hash[@sort]
diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml
index b15be0d861d..736b485bf06 100644
--- a/app/views/projects/builds/_header.html.haml
+++ b/app/views/projects/builds/_header.html.haml
@@ -1,8 +1,8 @@
.content-block.build-header
.header-content
- = render 'ci/status/badge', status: @build.detailed_status(current_user)
+ = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false
Build
- %strong ##{@build.id}
+ %strong.js-build-id ##{@build.id}
in pipeline
= link_to pipeline_path(@build.pipeline) do
%strong ##{@build.pipeline.id}
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index 0b3adcbe121..37bf085130a 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -22,14 +22,14 @@
%p.build-detail-row
The artifacts were removed
#{time_ago_with_tooltip(@build.artifacts_expire_at)}
- - elsif @build.artifacts_expire_at
+ - elsif @build.has_expiring_artifacts?
%p.build-detail-row
The artifacts will be removed in
%span.js-artifacts-remove= @build.artifacts_expire_at
- if @build.artifacts?
.btn-group.btn-group-justified{ role: :group }
- - if @build.artifacts_expire_at
+ - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build)
= link_to keep_namespace_project_build_artifacts_path(@project.namespace, @project, @build), class: 'btn btn-sm btn-default', method: :post do
Keep
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 54724ef5cab..c613e473e4c 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -51,8 +51,10 @@
.prepend-top-default
- if @build.erased?
.erased.alert.alert-warning
- - erased_by = "by #{link_to @build.erased_by.name, user_path(@build.erased_by)}" if @build.erased_by
- Build has been erased #{erased_by.html_safe} #{time_ago_with_tooltip(@build.erased_at)}
+ - if @build.erased_by_user?
+ Build has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
+ - else
+ Build has been erased #{time_ago_with_tooltip(@build.erased_at)}
- else
#js-build-scroll.scroll-controls
.scroll-step
diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml
index 324a7f8cd3f..762ff34a9ec 100644
--- a/app/views/projects/buttons/_download.html.haml
+++ b/app/views/projects/buttons/_download.html.haml
@@ -1,5 +1,5 @@
- if !project.empty_repo? && can?(current_user, :download_code, project)
- .project-action-button.dropdown.inline
+ .project-action-button.dropdown.inline>
%button.btn{ 'data-toggle' => 'dropdown' }
= icon('download')
= icon("caret-down")
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 520113639b7..c1e496455d1 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -85,7 +85,7 @@
- if build.finished_at
%p.finished-at
= icon("calendar")
- %span #{time_ago_with_tooltip(build.finished_at)}
+ %span= time_ago_with_tooltip(build.finished_at)
%td.coverage
- if coverage && build.try(:coverage)
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index e67492a36d1..818a70f38f1 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -47,21 +47,18 @@
- icon_status = "#{detailed_status.icon}_borderless"
- status_klass = "ci-status-icon ci-status-icon-#{detailed_status.group}"
- .stage-container.mini-pipeline-graph
- .dropdown.inline.build-content
- %button.has-tooltip.builds-dropdown.js-builds-dropdown-button{ type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: stage.name) } }
- %span.has-tooltip{ class: status_klass }
- %span.mini-pipeline-graph-icon-container
- %span{ class: status_klass }= custom_icon(icon_status)
- = icon('caret-down', class: 'dropdown-caret')
+ .stage-container.dropdown.js-mini-pipeline-graph
+ %button.mini-pipeline-graph-dropdown-toggle.has-tooltip.js-builds-dropdown-button{ class: "ci-status-icon-#{detailed_status.group}", type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: stage.name) } }
+ = custom_icon(icon_status)
+ = icon('caret-down')
- .js-builds-dropdown-container
- .dropdown-menu.grouped-pipeline-dropdown
- .arrow-up
- .js-builds-dropdown-list
+ %ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
+ .arrow-up
+ .js-builds-dropdown-list.scrollable-menu
+
+ .js-builds-dropdown-loading.builds-dropdown-loading.hidden
+ %span.fa.fa-spinner.fa-spin
- .js-builds-dropdown-loading.builds-dropdown-loading.hidden
- %span.fa.fa-spinner.fa-spin
%td
- if pipeline.duration
@@ -81,18 +78,18 @@
.btn-group.inline
- if actions.any?
.btn-group
- %a.dropdown-toggle.btn.btn-default.js-pipeline-dropdown-manual-actions{ type: 'button', 'data-toggle' => 'dropdown' }
+ %button.dropdown-toggle.btn.btn-default.has-tooltip.js-pipeline-dropdown-manual-actions{ type: 'button', title: 'Manual build', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Manual build' }
= custom_icon('icon_play')
- = icon('caret-down')
+ = icon('caret-down', 'aria-hidden' => 'true')
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |build|
%li
= link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do
= custom_icon('icon_play')
- %span= build.name.humanize
+ %span= build.name
- if artifacts.present?
.btn-group
- %a.dropdown-toggle.btn.btn-default.build-artifacts.js-pipeline-dropdown-download{ type: 'button', 'data-toggle' => 'dropdown' }
+ %button.dropdown-toggle.btn.btn-default.build-artifacts.has-tooltip.js-pipeline-dropdown-download{ type: 'button', title: 'Artifacts', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Artifacts' }
= icon("download")
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-align-right
@@ -105,8 +102,8 @@
- if can?(current_user, :update_pipeline, pipeline.project)
.cancel-retry-btns.inline
- if pipeline.retryable?
- = link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
+ = link_to retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn has-tooltip', title: 'Retry', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Retry' , method: :post do
= icon("repeat")
- if pipeline.cancelable?
- = link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: "Cancel", method: :post do
+ = link_to cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-remove has-tooltip', title: 'Cancel', data: { toggle: 'dropdown', placement: 'top' }, 'aria-label' => 'Cancel' , method: :post do
= icon("remove")
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index 12e4280d344..421b3db342d 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -13,7 +13,7 @@
%a.close{ href: "#", "data-dismiss" => "modal" } ×
%h3.page-title== #{label} this #{commit.change_type_title(current_user)}
.modal-body
- = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do
+ = form_tag [type.underscore, @project.namespace.becomes(Namespace), @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do
.form-group.branch
= label_tag 'target_branch', target_label, class: 'control-label'
.col-sm-10
diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml
index 13ab2253733..8aed88da38b 100644
--- a/app/views/projects/commit/_ci_menu.html.haml
+++ b/app/views/projects/commit/_ci_menu.html.haml
@@ -7,4 +7,4 @@
= nav_link(path: 'commit#pipelines') do
= link_to pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id) do
Pipelines
- %span.badge= @ci_pipelines.count
+ %span.badge= @commit.pipelines.size
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index a9ee9230076..6dba42d5226 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,8 +1,7 @@
.page-content-header
.header-main-content
- %strong
- = clipboard_button(clipboard_text: @commit.id)
- = @commit.short_id
+ %strong Commit #{@commit.short_id}
+ = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard")
%span.hidden-xs authored
#{time_ago_with_tooltip(@commit.authored_date)}
%span by
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index 5a9f7295135..1164627fa11 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -1,9 +1,9 @@
-%ul.content-list.pipelines
+%div
- if pipelines.blank?
- %li
+ %div
.nothing-here-block No pipelines to show
- else
- .table-holder
+ .table-holder.pipelines
%table.table.ci-table.js-pipeline-table
%thead
%th.pipeline-status Status
diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml
index 8233e26e4e7..00e7cdd1729 100644
--- a/app/views/projects/commit/pipelines.html.haml
+++ b/app/views/projects/commit/pipelines.html.haml
@@ -3,4 +3,4 @@
= render "commit_box"
= render "ci_menu"
-= render "pipelines_list", pipelines: @ci_pipelines
+= render "pipelines_list", pipelines: @commit.pipelines.order(id: :desc)
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index a940515fadf..002e3d345dc 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -9,33 +9,33 @@
- cache_key.push(commit.status(ref)) if commit.status(ref)
= cache(cache_key, expires_in: 1.day) do
- %li.commit.js-toggle-container{ id: "commit-#{commit.short_id}" }
- = author_avatar(commit, size: 36)
+ %li.commit.table-list-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
- .commit-info-block
- .commit-row-title
- %span.item-title
- = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message"
- %span.commit-row-message.visible-xs-inline
- &middot;
- = commit.short_id
- - if commit.status(ref)
- .visible-xs-inline
- = render_commit_status(commit, ref: ref)
- - if commit.description?
- %a.text-expander.hidden-xs.js-toggle-button ...
+ .table-list-cell.avatar-cell.hidden-xs
+ = author_avatar(commit, size: 36)
- .commit-actions.hidden-xs
- - if commit.status(ref)
- = render_commit_status(commit, ref: ref)
- = clipboard_button(clipboard_text: commit.id)
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
- = link_to_browse_code(project, commit)
+ .table-list-cell.commit-content
+ = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message item-title"
+ %span.commit-row-message.visible-xs-inline
+ &middot;
+ = commit.short_id
+ - if commit.status(ref)
+ .visible-xs-inline
+ = render_commit_status(commit, ref: ref)
+ - if commit.description?
+ %a.text-expander.hidden-xs.js-toggle-button ...
- if commit.description?
%pre.commit-row-description.js-toggle-content
= preserve(markdown(commit.description, pipeline: :single_line, author: commit.author))
+ .commiter
+ = commit_author_link(commit, avatar: false, size: 24)
+ committed
+ #{time_ago_with_tooltip(commit.committed_date)}
- = commit_author_link(commit, avatar: false, size: 24)
- committed
- #{time_ago_with_tooltip(commit.committed_date)}
+ .table-list-cell.commit-actions.hidden-xs
+ - if commit.status(ref)
+ = render_commit_status(commit, ref: ref)
+ = clipboard_button(clipboard_text: commit.id, title: "Copy commit SHA to clipboard")
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-short-id btn btn-transparent"
+ = link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml
index 6f5835cb9be..64d93e4141c 100644
--- a/app/views/projects/commits/_commit_list.html.haml
+++ b/app/views/projects/commits/_commit_list.html.haml
@@ -11,4 +11,4 @@
%li.warning-row.unstyled
#{number_with_delimiter(hidden)} additional commits have been omitted to prevent performance issues.
- else
- %ul.content-list= render commits, project: @project, ref: @ref
+ %ul.content-list.table-list= render commits, project: @project, ref: @ref
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index 48756c68941..904cdb5767f 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -2,9 +2,9 @@
- commits, hidden = limited_commits(@commits)
- commits.chunk { |c| c.committed_date.in_time_zone.to_date }.each do |day, commits|
- %li.commit-header= "#{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}"
+ %li.commit-header #{day.strftime('%d %b, %Y')} #{pluralize(commits.count, 'commit')}
%li.commits-row
- %ul.list-unstyled.commit-list
+ %ul.content-list.commit-list.table-list.table-wide
= render commits, project: project, ref: ref
- if hidden > 0
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index e77f23c7fd8..d94f23f5a38 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -9,10 +9,13 @@
= render "head"
%div{ class: container_class }
- .row-content-block.second-block.content-component-block
+ .row-content-block.second-block.content-component-block.flex-container-block
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits'
+ %ul.breadcrumb.repo-breadcrumb
+ = commits_breadcrumbs
+
.block-controls.hidden-xs.hidden-sm
- if @merge_request.present?
.control
@@ -30,8 +33,6 @@
.control
= link_to namespace_project_commits_path(@project.namespace, @project, @ref, { format: :atom, private_token: current_user.private_token }), title: "Commits Feed", class: 'btn' do
= icon("rss")
- %ul.breadcrumb.repo-breadcrumb
- = commits_breadcrumbs
%div{ id: dom_id(@project) }
%ol#commits-list.list-unstyled.content_list
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index 7bde20c3286..d76d48187cd 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -2,21 +2,21 @@
.clearfix
- if params[:to] && params[:from]
.compare-switch-container
- = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'}
+ = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Switch base of comparison'}
.form-group.dropdown.compare-form-group.from.js-compare-from-dropdown
.input-group.inline-input-group
%span.input-group-addon from
= hidden_field_tag :from, params[:from]
- = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
- .dropdown-toggle-text= params[:from] || 'Select branch/tag'
+ = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do
+ .dropdown-toggle-text.str-truncated= params[:from] || 'Select branch/tag'
= render "ref_dropdown"
.compare-ellipsis.inline ...
.form-group.dropdown.compare-form-group.to.js-compare-to-dropdown
.input-group.inline-input-group
%span.input-group-addon to
= hidden_field_tag :to, params[:to]
- = button_tag type: 'button', class: "form-control compare-dropdown-toggle js-compare-dropdown", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
- .dropdown-toggle-text= params[:to] || 'Select branch/tag'
+ = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip", required: true, data: { refs_url: refs_namespace_project_path(@project.namespace, @project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do
+ .dropdown-toggle-text.str-truncated= params[:to] || 'Select branch/tag'
= render "ref_dropdown"
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index 819e9bc15ae..9c8f58d4aea 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -16,9 +16,9 @@
There isn't anything to compare.
%p.slead
- if params[:to] == params[:from]
- %span.label-branch #{params[:from]}
+ %span.label-branch= params[:from]
and
- %span.label-branch #{params[:to]}
+ %span.label-branch= params[:to]
are the same.
- else
You'll need to use different branch names to get a valid comparison.
diff --git a/app/views/projects/cycle_analytics/_empty_stage.html.haml b/app/views/projects/cycle_analytics/_empty_stage.html.haml
index b200ce22970..c3f95860e92 100644
--- a/app/views/projects/cycle_analytics/_empty_stage.html.haml
+++ b/app/views/projects/cycle_analytics/_empty_stage.html.haml
@@ -2,6 +2,6 @@
.empty-stage
.icon-no-data
= custom_icon ('icon_no_data')
- %h4 We don’t have enough data to show this stage.
+ %h4 We don't have enough data to show this stage.
%p
{{currentStage.emptyStageText}}
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 89e2e162b5b..479ce44f378 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -28,8 +28,8 @@
.container-fluid
.row
.col-sm-3.col-xs-12.column{ "v-for" => "item in state.summary" }
- %h3.header {{item.value}}
- %p.text {{item.title}}
+ %h3.header {{ item.value }}
+ %p.text {{ item.title }}
.col-sm-3.col-xs-12.column
.dropdown.inline.js-ca-dropdown
%button.dropdown-menu-toggle{ "data-toggle" => "dropdown", :type => "button" }
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index 9238f232c7e..c468202569f 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -1,6 +1,6 @@
%tr.deployment
%td
- %strong= "##{deployment.iid}"
+ %strong ##{deployment.iid}
%td
= render 'projects/deployments/commit', deployment: deployment
@@ -8,7 +8,7 @@
%td.build-column
- if deployment.deployable
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
- = "#{deployment.deployable.name} (##{deployment.deployable.id})"
+ #{deployment.deployable.name} (##{deployment.deployable.id})
- if deployment.user
by
= user_avatar(user: deployment.user, size: 20)
diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml
index 52a1ece7d60..b87b79b170e 100644
--- a/app/views/projects/diffs/_content.html.haml
+++ b/app/views/projects/diffs/_content.html.haml
@@ -1,5 +1,5 @@
.diff-content.diff-wrap-lines
- - # Skip all non non-supported blobs
+ -# Skip all non non-supported blobs
- return unless blob.respond_to?(:text?)
- if diff_file.too_large?
.nothing-here-block This diff could not be displayed because it is too large.
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index ab4a2dc36e5..58c20e225c6 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -18,8 +18,8 @@
= parallel_diff_btn
= render 'projects/diffs/stats', diff_files: diff_files
-- if diff_files.overflow?
- = render 'projects/diffs/warning', diff_files: diff_files
+- if render_overflow_warning?(diff_files)
+ = render 'projects/diffs/warning', diff_files: diffs
.files{ data: { can_create_note: can_create_note } }
- diff_files.each_with_index do |diff_file|
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 15df2edefc7..c37a33bbcd5 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,5 +1,5 @@
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_commit.id) }
- .file-title{ id: "file-path-#{hexdigest(diff_file.file_path)}" }
+ .file-title
= render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "##{file_hash}"
- unless diff_file.submodule?
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index 90c9a0c6c2b..ddec775b789 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -25,4 +25,4 @@
- if diff_file.mode_changed?
%small
- = "#{diff_file.a_mode} → #{diff_file.b_mode}"
+ #{diff_file.a_mode} → #{diff_file.b_mode}
diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml
index 1bccaaf5273..ca10921c5e2 100644
--- a/app/views/projects/diffs/_image.html.haml
+++ b/app/views/projects/diffs/_image.html.haml
@@ -9,7 +9,7 @@
%span.wrap
.frame{ class: image_diff_class(diff) }
%img{ src: diff.deleted_file ? old_file_raw_path : file_raw_path, alt: diff.new_path }
- %p.image-info= "#{number_to_human_size file.size}"
+ %p.image-info= number_to_human_size(file.size)
- else
.image
.two-up.view
@@ -18,7 +18,7 @@
%a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) }
%img{ src: old_file_raw_path, alt: diff.old_path }
%p.image-info.hide
- %span.meta-filesize= "#{number_to_human_size old_file.size}"
+ %span.meta-filesize= number_to_human_size(old_file.size)
|
%b W:
%span.meta-width
@@ -30,7 +30,7 @@
%a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) }
%img{ src: file_raw_path, alt: diff.new_path }
%p.image-info.hide
- %span.meta-filesize= "#{number_to_human_size file.size}"
+ %span.meta-filesize= number_to_human_size(file.size)
|
%b W:
%span.meta-width
diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index b087485aa17..f361204ecac 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -14,7 +14,7 @@
- left_line_code = diff_file.line_code(left)
- left_position = diff_file.position(left)
%td.old_line.diff-line-num{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } }
- %a{ href: "##{left_line_code}" }= raw(left.old_pos)
+ %a{ href: "##{left_line_code}", data: { linenumber: left.old_pos } }
%td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text)
- else
%td.old_line.diff-line-num.empty-cell
@@ -27,7 +27,7 @@
- right_line_code = diff_file.line_code(right)
- right_position = diff_file.position(right)
%td.new_line.diff-line-num{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } }
- %a{ href: "##{right_line_code}" }= raw(right.new_pos)
+ %a{ href: "##{right_line_code}", data: { linenumber: right.new_pos } }
%td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text)
- else
%td.old_line.diff-line-num.empty-cell
diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml
index 290f696d582..8e24e28765f 100644
--- a/app/views/projects/diffs/_stats.html.haml
+++ b/app/views/projects/diffs/_stats.html.haml
@@ -2,7 +2,7 @@
.commit-stat-summary
Showing
= link_to '#', class: 'js-toggle-button' do
- %strong #{pluralize(diff_files.size, "changed file")}
+ %strong= pluralize(diff_files.size, "changed file")
with
%strong.cgreen #{diff_files.sum(&:added_lines)} additions
and
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 38e7fc4279c..84787155168 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -7,10 +7,17 @@
.project-edit-errors
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f|
%fieldset.append-bottom-0
- .form-group
- = f.label :name, class: 'label-light' do
- Project name
- = f.text_field :name, class: "form-control", id: "project_name_edit"
+ .row
+ .form-group.col-md-9
+ = f.label :name, class: 'label-light', for: 'project_name_edit' do
+ Project name
+ = f.text_field :name, class: "form-control", id: "project_name_edit"
+
+ .form-group.col-md-3
+ = f.label :id, class: 'label-light' do
+ Project ID
+ = f.text_field :id, class: 'form-control', readonly: true
+
.form-group
= f.label :description, class: 'label-light' do
Project description
@@ -21,76 +28,68 @@
.form-group
= f.label :default_branch, "Default Branch", class: 'label-light'
= f.select(:default_branch, @project.repository.branch_names, {}, {class: 'select2 select-wide'})
- .form-group.project-visibility-level-holder
- = f.label :visibility_level, class: 'label-light' do
- Visibility Level
- = link_to "(?)", help_page_path("public_access/public_access")
- - if can_change_visibility_level?(@project, current_user)
- = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: @project.visibility_level, form_model: @project)
- - else
- .info
- = visibility_level_icon(@project.visibility_level)
- %strong
- = visibility_level_label(@project.visibility_level)
- .light= visibility_level_description(@project.visibility_level, @project)
-
- .form-group
- = render 'shared/allow_request_access', form: f
-
.form-group
= f.label :tag_list, "Tags", class: 'label-light'
= f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control"
%p.help-block Separate tags with commas.
%hr
- %fieldset.features.append-bottom-0
+ %fieldset.append-bottom-0
%h5.prepend-top-0
- Feature Visibility
-
- = f.fields_for :project_feature do |feature_fields|
- .form_group.prepend-top-20
- .row
- .col-md-9
- = feature_fields.label :repository_access_level, "Repository", class: 'label-light'
- %span.help-block Push files to be stored in this project
- .col-md-3.js-repo-access-level
- = project_feature_access_select(:repository_access_level)
-
- .col-sm-12
- .row
- .col-md-9.project-feature-nested
- = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
- %span.help-block Submit changes to be merged upstream
- .col-md-3
- = project_feature_access_select(:merge_requests_access_level)
+ Sharing &amp; Permissions
+ .form_group.prepend-top-20.sharing-and-permissions
+ .row.js-visibility-select
+ .col-md-9
+ %label.label-light
+ = label_tag :project_visibility, 'Project Visibility', class: 'label-light'
+ = link_to "(?)", help_page_path("public_access/public_access")
+ %span.help-block
+ .col-md-3.visibility-select-container
+ = render('projects/visibility_select', model_method: :visibility_level, form: f, selected_level: @project.visibility_level)
+ = f.fields_for :project_feature do |feature_fields|
+ %fieldset.features
+ .row
+ .col-md-9.project-feature
+ = feature_fields.label :repository_access_level, "Repository", class: 'label-light'
+ %span.help-block View and edit files in this project
+ .col-md-3.js-repo-access-level
+ = project_feature_access_select(:repository_access_level)
- .row
- .col-md-9.project-feature-nested
- = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
- %span.help-block Submit, test and deploy your changes before merge
- .col-md-3
- = project_feature_access_select(:builds_access_level)
+ .row
+ .col-md-9.project-feature.nested
+ = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
+ %span.help-block Submit changes to be merged upstream
+ .col-md-3
+ = project_feature_access_select(:merge_requests_access_level)
- .row
- .col-md-9
- = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
- %span.help-block Share code pastes with others out of Git repository
- .col-md-3
- = project_feature_access_select(:snippets_access_level)
+ .row
+ .col-md-9.project-feature.nested
+ = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
+ %span.help-block Submit, test and deploy your changes before merge
+ .col-md-3
+ = project_feature_access_select(:builds_access_level)
- .row
- .col-md-9
- = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
- %span.help-block Lightweight issue tracking system for this project
- .col-md-3
- = project_feature_access_select(:issues_access_level)
+ .row
+ .col-md-9.project-feature
+ = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
+ %span.help-block Share code pastes with others out of Git repository
+ .col-md-3
+ = project_feature_access_select(:snippets_access_level)
- .row
- .col-md-9
- = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
- %span.help-block Pages for project documentation
- .col-md-3
- = project_feature_access_select(:wiki_access_level)
+ .row
+ .col-md-9.project-feature
+ = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
+ %span.help-block Lightweight issue tracking system for this project
+ .col-md-3
+ = project_feature_access_select(:issues_access_level)
+ .row
+ .col-md-9.project-feature
+ = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
+ %span.help-block Pages for project documentation
+ .col-md-3
+ = project_feature_access_select(:wiki_access_level)
+ .form-group
+ = render 'shared/allow_request_access', form: f
- if Gitlab.config.lfs.enabled && current_user.admin?
.row
.col-md-9
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 3525a07a687..58c085cdb9d 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -52,7 +52,7 @@
git push -u origin master
%fieldset
- %h5 Existing folder or Git repository
+ %h5 Existing folder
%pre.light-well
:preserve
cd existing_folder
@@ -62,6 +62,15 @@
git commit
git push -u origin master
+ %fieldset
+ %h5 Existing Git repository
+ %pre.light-well
+ :preserve
+ cd existing_repo
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')}
+ git push -u origin --all
+ git push -u origin --tags
+
- if can? current_user, :remove_project, @project
.prepend-top-20
= link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index 6c8a6f051a9..f4aa523b32d 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -1,7 +1,7 @@
.top-area
.nav-text
- full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private"
- = "#{pluralize(@total_forks_count, 'fork')}: #{full_count_title}"
+ #{pluralize(@total_forks_count, 'fork')}: #{full_count_title}
.nav-controls
= form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
index 6d7af1685fd..07fb80750d6 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml
@@ -77,7 +77,7 @@
- if generic_commit_status.finished_at
%p.finished-at
= icon("calendar")
- %span #{time_ago_with_tooltip(generic_commit_status.finished_at)}
+ %span= time_ago_with_tooltip(generic_commit_status.finished_at)
%td.coverage
- if coverage && generic_commit_status.try(:coverage)
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index 7e34a89f9ae..c8a82f7bca3 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -11,7 +11,7 @@
%p.lead
Commit statistics for
- %strong #{@ref}
+ %strong= @ref
#{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
.row
@@ -19,19 +19,19 @@
%ul
%li
%p.lead
- %strong #{@commits_graph.commits.size}
+ %strong= @commits_graph.commits.size
commits during
- %strong #{@commits_graph.duration}
+ %strong= @commits_graph.duration
days
%li
%p.lead
Average
- %strong #{@commits_graph.commit_per_day}
+ %strong= @commits_graph.commit_per_day
commits per day
%li
%p.lead
Contributed by
- %strong #{@commits_graph.authors}
+ %strong= @commits_graph.authors
authors
.col-md-6
%div
diff --git a/app/views/projects/group_links/index.html.haml b/app/views/projects/group_links/_index.html.haml
index 1b0dbbb8111..99d0df2ac34 100644
--- a/app/views/projects/group_links/index.html.haml
+++ b/app/views/projects/group_links/_index.html.haml
@@ -20,10 +20,10 @@
.form-group
= label_tag :expires_at, 'Access expiration date', class: 'label-light'
.clearable-input
- = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Select access expiration date'
+ = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date-groups', placeholder: 'Select access expiration date', id: 'expires_at_groups'
%i.clear-icon.js-clear-input
.help-block
- On this date, all users in the group will automatically lose access to this project.
+ On this date, all members in the group will automatically lose access to this project.
= submit_tag "Share", class: "btn btn-create"
.col-lg-9.col-lg-offset-3
%hr
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/_index.html.haml
index 8faad351463..8faad351463 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/_index.html.haml
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 26f3f0ac292..5fbed8b9ab8 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -6,6 +6,9 @@
= content_for :sub_nav do
= render "projects/issues/head"
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('filtered_search/filtered_search_bundle.js')
+
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@project.name} issues")
@@ -16,11 +19,8 @@
= render 'shared/issuable/nav', type: :issues
.nav-controls
- if current_user
- = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10' do
+ = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do
= icon('rss')
- %span.icon-label
- Subscribe
- = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace,
@project,
@@ -30,7 +30,7 @@
title: "New Issue",
id: "new_issue_link" do
New Issue
- = render 'shared/issuable/filter', type: :issues
+ = render 'shared/issuable/search_bar', type: :issues
.issues-holder
= render 'issues'
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 981bf640a6b..9fa00811af0 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,7 +1,9 @@
-- @content_class = "limit-container-width"
+- @content_class = "limit-container-width" unless fluid_layout
- page_title "#{@issue.title} (#{@issue.to_reference})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/vue_resource.js')
.clearfix.detail-page-header
.issuable-header
diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml
index 24e86b8497f..a80f9aa4c4a 100644
--- a/app/views/projects/mattermosts/_team_selection.html.haml
+++ b/app/views/projects/mattermosts/_team_selection.html.haml
@@ -7,20 +7,21 @@
%p
= @teams.one? ? 'The team' : 'Select the team'
where the slash commands will be used in
- - selected_id = @teams.keys.first if @teams.one?
+ - selected_id = @teams.one? ? @teams.keys.first : 0
- options = mattermost_teams_options(@teams)
- options = options_for_select(options, selected_id)
- = f.select(:team_id, options, {}, { class: 'form-control', selected: "#{selected_id}" })
+ = f.select(:team_id, options, {}, { class: 'form-control', disabled: @teams.one?, selected: selected_id })
+ = f.hidden_field(:team_id, value: selected_id) if @teams.one?
.help-block
- if @teams.one?
- This is the only team where you are an administrator.
+ This is the only available team.
- else
- The list shows teams where you are administrator
- To create a team, ask your Mattermost system administrator.
+ The list shows all available teams.
To create a team,
= link_to "#{Gitlab.config.mattermost.host}/create_team" do
use Mattermost's interface
= icon('external-link')
+ or ask your Mattermost system administrator.
%hr
%h4 Command trigger word
%p Choose the word that will trigger commands
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 34ead6427e0..d3c013b3f21 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -3,15 +3,15 @@
%p.slead
- source_title, target_title = format_mr_branch_names(@merge_request)
From
- %strong.label-branch #{source_title}
+ %strong.label-branch= source_title
%span into
- %strong.label-branch #{target_title}
+ %strong.label-branch= target_title
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f|
- = render 'shared/issuable/form', f: f, issuable: @merge_request
+ = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits
= f.hidden_field :source_project_id
= f.hidden_field :source_branch
= f.hidden_field :target_project_id
@@ -43,7 +43,7 @@
#commits.commits.tab-pane.active
= render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane
- - # This tab is always loaded via AJAX
+ -# This tab is always loaded via AJAX
- if @pipelines.any?
#pipelines.pipelines.tab-pane
= render "projects/merge_requests/show/pipelines"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 633701c6f40..9585a9a3ad4 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,8 +1,9 @@
-- @content_class = "limit-container-width"
+- @content_class = "limit-container-width" unless fluid_layout
- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests"
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/vue_resource.js')
= page_specific_javascript_tag('diff_notes/diff_notes_bundle.js')
.merge-request{ 'data-url' => merge_request_path(@merge_request) }
@@ -42,64 +43,63 @@
= render "projects/merge_requests/widget/show.html.haml"
- if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user)
- .merge-manually.light.prepend-top-default.append-bottom-default
+ .merge-manually.light.prepend-top-default
You can also accept this merge request manually using the
= succeed '.' do
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
- .content-block.content-block-small
+ .content-block.content-block-small.emoji-list-container
= render 'award_emoji/awards_block', awardable: @merge_request, inline: true
- - if @commits_count.nonzero?
- .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
- %div{ class: container_class }
- %ul.merge-request-tabs.nav-links.no-top.no-bottom
- %li.notes-tab
- = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
- Discussion
- %span.badge= @merge_request.related_notes.user.count
- - if @merge_request.source_project
- %li.commits-tab
- = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
- Commits
- %span.badge= @commits_count
- - if @pipelines.any?
- %li.pipelines-tab
- = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
- Pipelines
- %span.badge= @pipelines.size
- %li.diffs-tab
- = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
- Changes
- %span.badge= @merge_request.diff_size
- %li#resolve-count-app.line-resolve-all-container.pull-right.prepend-top-10.hidden-xs{ "v-cloak" => true }
- %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
- %div
- .line-resolve-all{ "v-show" => "discussionCount > 0",
- ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
- %span.line-resolve-btn.is-disabled{ type: "button",
- ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
- = render "shared/icons/icon_status_success.svg"
- %span.line-resolve-text
- {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
- = render "discussions/jump_to_next"
+ .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
+ .merge-request-tabs-container
+ %ul.merge-request-tabs.nav-links.no-top.no-bottom
+ %li.notes-tab
+ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do
+ Discussion
+ %span.badge= @merge_request.related_notes.user.count
+ - if @merge_request.source_project
+ %li.commits-tab
+ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do
+ Commits
+ %span.badge= @commits_count
+ - if @pipelines.any?
+ %li.pipelines-tab
+ = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do
+ Pipelines
+ %span.badge= @pipelines.size
+ %li.diffs-tab
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do
+ Changes
+ %span.badge= @merge_request.diff_size
+ %li#resolve-count-app.line-resolve-all-container.pull-right.prepend-top-10.hidden-xs{ "v-cloak" => true }
+ %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" }
+ %div
+ .line-resolve-all{ "v-show" => "discussionCount > 0",
+ ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" }
+ %span.line-resolve-btn.is-disabled{ type: "button",
+ ":class" => "{ 'is-active': resolvedDiscussionCount === discussionCount }" }
+ = render "shared/icons/icon_status_success.svg"
+ %span.line-resolve-text
+ {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ resolvedCountText }} resolved
+ = render "discussions/jump_to_next"
- .tab-content#diff-notes-app
- #notes.notes.tab-pane.voting_notes
- .row
- %section.col-md-12
- .issuable-discussion
- = render "projects/merge_requests/discussion"
+ .tab-content#diff-notes-app
+ #notes.notes.tab-pane.voting_notes
+ .row
+ %section.col-md-12
+ .issuable-discussion
+ = render "projects/merge_requests/discussion"
- #commits.commits.tab-pane
- - # This tab is always loaded via AJAX
- #pipelines.pipelines.tab-pane
- - # This tab is always loaded via AJAX
- #diffs.diffs.tab-pane
- - # This tab is always loaded via AJAX
+ #commits.commits.tab-pane
+ -# This tab is always loaded via AJAX
+ #pipelines.pipelines.tab-pane
+ -# This tab is always loaded via AJAX
+ #diffs.diffs.tab-pane
+ -# This tab is always loaded via AJAX
- .mr-loading-status
- = spinner
+ .mr-loading-status
+ = spinner
= render 'shared/issuable/sidebar', issuable: @merge_request
- if @merge_request.can_be_reverted?(current_user)
@@ -113,3 +113,5 @@
merge_request = new MergeRequest({
action: "#{controller.action_name}"
});
+
+ var mrRefreshWidgetUrl = "#{mr_widget_refresh_url(@merge_request)}";
diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml
index b8b87dcdcaf..ebef2157d34 100644
--- a/app/views/projects/merge_requests/conflicts.html.haml
+++ b/app/views/projects/merge_requests/conflicts.html.haml
@@ -1,5 +1,6 @@
- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('lib/vue_resource.js')
= page_specific_javascript_tag('merge_conflicts/merge_conflicts_bundle.js')
= page_specific_javascript_tag('lib/ace.js')
= render "projects/merge_requests/show/mr_title"
diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml
index baa1ade5eee..11793919ff7 100644
--- a/app/views/projects/merge_requests/show/_commits.html.haml
+++ b/app/views/projects/merge_requests/show/_commits.html.haml
@@ -1,2 +1,8 @@
-%ol#commits-list.list-unstyled
- = render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch
+- if @commits.empty?
+ .commits-empty
+ %h4
+ There are no commits yet.
+ = custom_icon ('illustration_no_commits')
+- else
+ %ol#commits-list.list-unstyled
+ = render "projects/commits/commits", project: @merge_request.source_project, ref: @merge_request.source_branch
diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml
index 99c71e1454a..5f048d04b27 100644
--- a/app/views/projects/merge_requests/show/_diffs.html.haml
+++ b/app/views/projects/merge_requests/show/_diffs.html.haml
@@ -1,13 +1,5 @@
-- if @merge_request_diff.collected?
+- if @merge_request_diff.collected? || @merge_request_diff.overflow?
= render 'projects/merge_requests/show/versions'
= render "projects/diffs/diffs", diffs: @diffs
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
-- else
- .alert.alert-warning
- %h4
- Changes view for this comparison is extremely large.
- %p
- You can
- = link_to "download it", merge_request_path(@merge_request, format: :diff), class: "vlink"
- instead.
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index ec76c6a5417..93ed4b68e0e 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -8,7 +8,7 @@
%p
%strong Step 1.
Fetch and check out the branch for this merge request
- = clipboard_button(clipboard_target: "pre#merge-info-1")
+ = clipboard_button(clipboard_target: "pre#merge-info-1", title: "Copy commands to clipboard")
%pre.dark#merge-info-1
- if @merge_request.for_fork?
:preserve
@@ -25,7 +25,7 @@
%p
%strong Step 3.
Merge the branch and fix any conflicts that come up
- = clipboard_button(clipboard_target: "pre#merge-info-3")
+ = clipboard_button(clipboard_target: "pre#merge-info-3", title: "Copy commands to clipboard")
%pre.dark#merge-info-3
- if @merge_request.for_fork?
:preserve
@@ -38,7 +38,7 @@
%p
%strong Step 4.
Push the result of the merge to GitLab
- = clipboard_button(clipboard_target: "pre#merge-info-4")
+ = clipboard_button(clipboard_target: "pre#merge-info-4", title: "Copy commands to clipboard")
%pre.dark#merge-info-4
:preserve
git push origin #{h @merge_request.target_branch}
diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml
index ac4a03220b9..74a7b1dc498 100644
--- a/app/views/projects/merge_requests/show/_versions.html.haml
+++ b/app/views/projects/merge_requests/show/_versions.html.haml
@@ -19,13 +19,13 @@
%ul
- @merge_request_diffs.each do |merge_request_diff|
%li
- = link_to merge_request_version_path(@project, @merge_request, merge_request_diff), class: ('is-active' if merge_request_diff == @merge_request_diff) do
+ = link_to merge_request_version_path(@project, @merge_request, merge_request_diff, @start_sha), class: ('is-active' if merge_request_diff == @merge_request_diff) do
%strong
- if merge_request_diff.latest?
latest version
- else
version #{version_index(merge_request_diff)}
- .monospace #{short_sha(merge_request_diff.head_commit_sha)}
+ .monospace= short_sha(merge_request_diff.head_commit_sha)
%small
#{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)},
= time_ago_with_tooltip(merge_request_diff.created_at)
@@ -55,14 +55,14 @@
latest version
- else
version #{version_index(merge_request_diff)}
- .monospace #{short_sha(merge_request_diff.head_commit_sha)}
+ .monospace= short_sha(merge_request_diff.head_commit_sha)
%small
= time_ago_with_tooltip(merge_request_diff.created_at)
%li
= link_to merge_request_version_path(@project, @merge_request, @merge_request_diff), class: ('is-active' unless @start_sha) do
%strong
#{@merge_request.target_branch} (base)
- .monospace #{short_sha(@merge_request_diff.base_commit_sha)}
+ .monospace= short_sha(@merge_request_diff.base_commit_sha)
- if different_base?(@start_version, @merge_request_diff)
.content-block
@@ -72,7 +72,7 @@
= link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do
new commits
from
- %code #{@merge_request.target_branch}
+ %code= @merge_request.target_branch
- unless @merge_request_diff.latest? && !@start_sha
.comments-disabled-notif.content-block
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index c80dc33058d..5faa6c43f9f 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -13,8 +13,8 @@
%span.ci-coverage
- elsif @merge_request.has_ci?
- - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
- - # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API
+ -# Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX
+ -# TODO, remove in later versions when services like Jenkins will set CI status via Commit status API
.mr-widget-heading
- %w[success skipped canceled failed running pending].each do |status|
.ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: "display:none" }
diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
index 072d01d144e..f70cd09c5f4 100644
--- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml
@@ -1,3 +1,6 @@
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('merge_request_widget/ci_bundle.js')
+
%h4
Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)}
to be merged automatically when the pipeline succeeds.
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index 8620f492282..e8e450742b5 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -1,11 +1,14 @@
.note-edit-form
- = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true, html: { class: 'edit-note common-note-form js-quick-submit' } do |f|
- = note_target_fields(note)
- = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview' } do
- = render 'projects/zen', f: f, attr: :note, classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..."
+ = form_tag '#', method: :put, class: 'edit-note common-note-form js-quick-submit' do
+ = hidden_field_tag :target_id, '', class: 'js-form-target-id'
+ = hidden_field_tag :target_type, '', class: 'js-form-target-type'
+ = render layout: 'projects/md_preview', locals: { preview_class: 'md-preview', referenced_users: true } do
+ = render 'projects/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', placeholder: "Write a comment or drag your files here..."
= render 'projects/notes/hints'
.note-form-actions.clearfix
- = f.submit 'Save Comment', class: 'btn btn-nr btn-save js-comment-button'
+ .settings-message.note-edit-warning.js-edit-warning
+ Finish editing this message first!
+ = submit_tag 'Save Comment', class: 'btn btn-nr btn-save js-comment-button'
%button.btn.btn-nr.btn-cancel.note-edit-cancel{ type: 'button' }
Cancel
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 39731668a61..b561052e721 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -3,6 +3,7 @@
= form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new-note js-new-note-form js-quick-submit common-note-form", "data-noteable-iid" => @note.noteable.try(:iid), }, authenticity_token: true do |f|
= hidden_field_tag :view, diff_view
= hidden_field_tag :line_type
+ = hidden_field_tag :merge_request_diff_head_sha, @note.noteable.try(:diff_head_sha)
= note_target_fields(@note)
= f.hidden_field :commit_id
= f.hidden_field :line_code
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index eb869ea85bf..09339e520dd 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -67,7 +67,9 @@
= note.redacted_note_html
= edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true)
- if note_editable
- = render 'projects/notes/edit_form', note: note
+ .original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } }
+ #{note.note}
+ %textarea.hidden.js-task-list-field.original-task-list= note.note
.note-awards
= render 'award_emoji/awards_block', awardable: note, inline: false
- if note.system
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 00b62a595ff..fbd2bff5bbb 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -1,5 +1,8 @@
%ul#notes-list.notes.main-notes-list.timeline
= render "projects/notes/notes"
+
+= render 'projects/notes/edit_form'
+
%ul.notes.notes-form.timeline
%li.timeline-entry
.flash-container.timeline-content
@@ -20,4 +23,4 @@
to post a comment
:javascript
- var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
+ var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, project_id: @project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
diff --git a/app/views/projects/pipelines/_stage.html.haml b/app/views/projects/pipelines/_stage.html.haml
index cf1b366bf2c..a0b14a7274a 100644
--- a/app/views/projects/pipelines/_stage.html.haml
+++ b/app/views/projects/pipelines/_stage.html.haml
@@ -1,4 +1,3 @@
-%ul
- - @stage.statuses.latest.each do |status|
- %li.dropdown-build
- = render 'ci/status/graph_badge', subject: status
+- @stage.statuses.latest.each do |status|
+ %li
+ = render 'ci/status/dropdown_graph_badge', subject: status
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index 4bb3d4d35fb..df36279ed75 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -35,21 +35,33 @@
= link_to ci_lint_path, class: 'btn btn-default' do
%span CI Lint
-
- .content-list.pipelines
+ .content-list.pipelines{ data: { url: namespace_project_pipelines_path(@project.namespace, @project, format: :json) } }
- if @pipelines.blank?
%div
.nothing-here-block No pipelines to show
- else
- .table-holder
- %table.table.ci-table.js-pipeline-table
- %thead
- %th.pipeline-status Status
- %th.pipeline-info Pipeline
- %th.pipeline-commit Commit
- %th.pipeline-stages Stages
- %th.pipeline-date
- %th.pipeline-actions.hidden-xs
- = render @pipelines, commit_sha: true, stage: true, allow_retry: true
-
- = paginate @pipelines, theme: 'gitlab'
+ .pipeline-svgs{ "data" => {"commit_icon_svg" => custom_icon("icon_commit"),
+ "icon_status_canceled" => custom_icon("icon_status_canceled"),
+ "icon_status_running" => custom_icon("icon_status_running"),
+ "icon_status_skipped" => custom_icon("icon_status_skipped"),
+ "icon_status_created" => custom_icon("icon_status_created"),
+ "icon_status_pending" => custom_icon("icon_status_pending"),
+ "icon_status_success" => custom_icon("icon_status_success"),
+ "icon_status_failed" => custom_icon("icon_status_failed"),
+ "icon_status_warning" => custom_icon("icon_status_warning"),
+ "stage_icon_status_canceled" => custom_icon("icon_status_canceled_borderless"),
+ "stage_icon_status_running" => custom_icon("icon_status_running_borderless"),
+ "stage_icon_status_skipped" => custom_icon("icon_status_skipped_borderless"),
+ "stage_icon_status_created" => custom_icon("icon_status_created_borderless"),
+ "stage_icon_status_pending" => custom_icon("icon_status_pending_borderless"),
+ "stage_icon_status_success" => custom_icon("icon_status_success_borderless"),
+ "stage_icon_status_failed" => custom_icon("icon_status_failed_borderless"),
+ "stage_icon_status_warning" => custom_icon("icon_status_warning_borderless"),
+ "icon_play" => custom_icon("icon_play"),
+ "icon_timer" => custom_icon("icon_timer"),
+ "icon_status_manual" => custom_icon("icon_status_manual"),
+ } }
+
+ .vue-pipelines-index
+
+= page_specific_javascript_tag('vue_pipelines_index/index.js')
diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml
index 9738f369a35..c7996077bc7 100644
--- a/app/views/projects/project_members/_group_members.html.haml
+++ b/app/views/projects/project_members/_group_members.html.haml
@@ -1,7 +1,7 @@
.panel.panel-default
.panel-heading
Group members with access to
- %strong #{@group.name}
+ %strong= @group.name
%span.badge= members.size
- if can?(current_user, :admin_group_member, @group)
.controls
diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml
index d7f5fa96527..fdeb5f21fbe 100644
--- a/app/views/projects/project_members/_groups.html.haml
+++ b/app/views/projects/project_members/_groups.html.haml
@@ -1,7 +1,7 @@
.panel.panel-default.project-members-groups
.panel-heading
Groups with access to
- %strong #{@project.name}
+ %strong= @project.name
%span.badge= group_links.size
%ul.content-list
= render partial: 'shared/members/group', collection: group_links, as: :group_link
diff --git a/app/views/projects/project_members/_index.html.haml b/app/views/projects/project_members/_index.html.haml
new file mode 100644
index 00000000000..ab0771b5751
--- /dev/null
+++ b/app/views/projects/project_members/_index.html.haml
@@ -0,0 +1,22 @@
+.row.prepend-top-default
+ .col-lg-3.settings-sidebar
+ %h4.prepend-top-0
+ Members
+ - if can?(current_user, :admin_project_member, @project)
+ %p
+ Add a new member to
+ %strong= @project.name
+ .col-lg-9
+ .light.prepend-top-default
+ - if can?(current_user, :admin_project_member, @project)
+ = render "projects/project_members/new_project_member"
+
+ = render 'shared/members/requests', membership_source: @project, requesters: @requesters
+ .append-bottom-default.clearfix
+ %h5.member.existing-title
+ Existing members and groups
+ - if @group_links.any?
+ = render 'projects/project_members/groups', group_links: @group_links
+
+ = render 'projects/project_members/team', members: @project_members
+ = paginate @project_members, theme: "gitlab"
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 79dcd7a6ee9..2b1c23f7dda 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -1,22 +1,18 @@
= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'users-project-form' } do |f|
- .row
- .col-md-4.col-lg-6
- = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true)
- .help-block.append-bottom-10
- Search for users by name, username, or email, or invite new ones using their email address.
-
- .col-md-3.col-lg-2
- = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select"
- .help-block.append-bottom-10
- = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
- about role permissions
-
- .col-md-3.col-lg-2
- .clearable-input
- = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
- %i.clear-icon.js-clear-input
- .help-block.append-bottom-10
- On this date, the user(s) will automatically lose access to this project.
-
- .col-md-2
- = f.submit "Add to project", class: "btn btn-create btn-block"
+ .form-group
+ = users_select_tag(:user_ids, multiple: true, class: "input-clamp", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
+ .help-block.append-bottom-10
+ Search for members by name, username, or email, or invite new ones using their email address.
+ .form-group
+ = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select"
+ .help-block.append-bottom-10
+ = link_to "Read more", help_page_path("user/permissions"), class: "vlink"
+ about role permissions
+ .form-group
+ .clearable-input
+ = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
+ %i.clear-icon.js-clear-input
+ .help-block.append-bottom-10
+ On this date, the member(s) will automatically lose access to this project.
+ = f.submit "Add to project", class: "btn btn-create"
+ = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default", title: "Import members from another project"
diff --git a/app/views/projects/project_members/_shared_group_members.html.haml b/app/views/projects/project_members/_shared_group_members.html.haml
index 77370c14def..7902ddb1ae9 100644
--- a/app/views/projects/project_members/_shared_group_members.html.haml
+++ b/app/views/projects/project_members/_shared_group_members.html.haml
@@ -5,9 +5,9 @@
.panel.panel-default
.panel-heading
Shared with
- %strong #{shared_group.name}
+ %strong= shared_group.name
group, members with
- %strong #{group_links.human_access}
+ %strong= group_links.human_access
role (#{shared_group_users_count})
- if can?(current_user, :admin_group, shared_group)
.panel-head-actions
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index c1e894d8f40..81d57c77edf 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -1,7 +1,13 @@
.panel.panel-default
.panel-heading
- Users with access to
- %strong #{@project.name}
+ Members with access to
+ %strong= @project.name
%span.badge= @project_members.total_count
+ = form_tag namespace_project_settings_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
+ .form-group
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
+ %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
+ = icon("search")
+ = render 'shared/members/sort_dropdown'
%ul.content-list
= render partial: 'shared/members/member', collection: members, as: :member
diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml
index eef97107d77..42ce4f8001b 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -12,5 +12,4 @@
.form-actions
= button_tag 'Import project members', class: "btn btn-create"
- = link_to "Cancel", namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-cancel"
-
+ = link_to "Cancel", namespace_project_settings_members_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
deleted file mode 100644
index 4f1cec20f85..00000000000
--- a/app/views/projects/project_members/index.html.haml
+++ /dev/null
@@ -1,29 +0,0 @@
-- page_title "Members"
-
-.project-members-page.prepend-top-default
- %h4.project-members-title.clearfix
- Members
- = link_to "Import", import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-default pull-right hidden-xs", title: "Import members from another project"
- - if can?(current_user, :admin_project_member, @project)
- .project-members-new.append-bottom-default
- %p.clearfix
- Add new user to
- %strong= @project.name
- = render "new_project_member"
-
- = render 'shared/members/requests', membership_source: @project, requesters: @requesters
-
- .append-bottom-default.clearfix
- %h5.member.existing-title
- Existing users and groups
- = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
- .form-group
- = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
- %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
- = icon("search")
- = render 'shared/members/sort_dropdown'
- - if @group_links.any?
- = render 'groups', group_links: @group_links
-
- = render 'team', members: @project_members
- = paginate @project_members, theme: "gitlab"
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index 33d5cbff420..79d8d721aa9 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -7,7 +7,7 @@
.oneline
.title
Release notes for tag
- %strong #{@tag.name}
+ %strong= @tag.name
= form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f|
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 51b0939564e..dcff675eafc 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -9,10 +9,10 @@
(checkout the #{link_to 'GitLab Runner section', 'https://about.gitlab.com/gitlab-ci/#gitlab-runner', target: '_blank'} for information on how to install it).
%li
Specify the following URL during the Runner setup:
- %code #{ci_root_url(only_path: false)}
+ %code= ci_root_url(only_path: false)
%li
Use the following registration token during setup:
- %code #{@project.runners_token}
+ %code= @project.runners_token
%li
Start the Runner!
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index fc338dcf887..f1a80f1d5e1 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -17,4 +17,4 @@
- disabled_title = @service.disabled_title
= link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service), class: "btn #{disabled_class}", title: disabled_title
- = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel"
+ = link_to "Cancel", namespace_project_settings_integrations_path(@project.namespace, @project), class: "btn btn-cancel"
diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/_index.html.haml
index 66fd3029dc9..964133504e6 100644
--- a/app/views/projects/services/index.html.haml
+++ b/app/views/projects/services/_index.html.haml
@@ -1,5 +1,3 @@
-- page_title "Services"
-
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
index 63b797cd391..c1e576b42fc 100644
--- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml
@@ -8,8 +8,8 @@
by entering
%code /&lt;command_trigger_word&gt; help
- - unless enabled
+ - unless enabled || @service.template?
= render 'projects/services/mattermost_slash_commands/detailed_help', subject: @service
-- if enabled
+- if enabled && !@service.template?
= render 'projects/services/mattermost_slash_commands/installation_info', subject: @service
diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
index c929eee3bb9..fcc91be11cd 100644
--- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
+++ b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml
@@ -4,4 +4,4 @@
.col-sm-9.col-sm-offset-3
= link_to new_namespace_project_mattermost_path(@project.namespace, @project), class: 'btn btn-lg' do
= custom_icon('mattermost_logo', size: 15)
- = 'Add to Mattermost'
+ Add to Mattermost
diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml
index c45052e3954..04b9100acc6 100644
--- a/app/views/projects/services/slack_slash_commands/_help.html.haml
+++ b/app/views/projects/services/slack_slash_commands/_help.html.haml
@@ -1,4 +1,5 @@
-- run_actions_text = "Perform common operations on this project: #{@project.name_with_namespace}"
+- pretty_name = defined?(@project) ? @project.name_with_namespace : "namespace / path"
+- run_actions_text = "Perform common operations on this project: #{pretty_name}"
.well
This service allows GitLab users to perform common operations on this
@@ -9,85 +10,86 @@
%code /&lt;command&gt; help
%br
%br
- To setup this service:
- %ul.list-unstyled
- %li
- 1.
- = link_to 'Add a slash command', 'https://my.slack.com/services/new/slash-commands'
- in your Slack team with these options:
+ - unless @service.template?
+ To setup this service:
+ %ul.list-unstyled
+ %li
+ 1.
+ = link_to 'Add a slash command', 'https://my.slack.com/services/new/slash-commands'
+ in your Slack team with these options:
- %hr
+ %hr
- .help-form
- .form-group
- = label_tag nil, 'Command', class: 'col-sm-2 col-xs-12 control-label'
- .col-sm-10.col-xs-12.text-block
- %p Fill in the word that works best for your team.
- %p
- Suggestions:
- %code= 'gitlab'
- %code= @project.path # Path contains no spaces, but dashes
- %code= @project.path_with_namespace
+ .help-form
+ .form-group
+ = label_tag nil, 'Command', class: 'col-sm-2 col-xs-12 control-label'
+ .col-sm-10.col-xs-12.text-block
+ %p Fill in the word that works best for your team.
+ %p
+ Suggestions:
+ %code= 'gitlab'
+ %code= @project.path # Path contains no spaces, but dashes
+ %code= @project.path_with_namespace
- .form-group
- = label_tag :url, 'URL', class: 'col-sm-2 col-xs-12 control-label'
- .col-sm-10.col-xs-12.input-group
- = text_field_tag :url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly'
- .input-group-btn
- = clipboard_button(clipboard_target: '#url')
+ .form-group
+ = label_tag :url, 'URL', class: 'col-sm-2 col-xs-12 control-label'
+ .col-sm-10.col-xs-12.input-group
+ = text_field_tag :url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly'
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#url')
- .form-group
- = label_tag nil, 'Method', class: 'col-sm-2 col-xs-12 control-label'
- .col-sm-10.col-xs-12.text-block POST
+ .form-group
+ = label_tag nil, 'Method', class: 'col-sm-2 col-xs-12 control-label'
+ .col-sm-10.col-xs-12.text-block POST
- .form-group
- = label_tag :customize_name, 'Customize name', class: 'col-sm-2 col-xs-12 control-label'
- .col-sm-10.col-xs-12.input-group
- = text_field_tag :customize_name, 'GitLab', class: 'form-control input-sm', readonly: 'readonly'
- .input-group-btn
- = clipboard_button(clipboard_target: '#customize_name')
+ .form-group
+ = label_tag :customize_name, 'Customize name', class: 'col-sm-2 col-xs-12 control-label'
+ .col-sm-10.col-xs-12.input-group
+ = text_field_tag :customize_name, 'GitLab', class: 'form-control input-sm', readonly: 'readonly'
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#customize_name')
- .form-group
- = label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label'
- .col-sm-10.col-xs-12.text-block
- = image_tag(asset_url('gitlab_logo.png'), width: 36, height: 36)
- = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank')
+ .form-group
+ = label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label'
+ .col-sm-10.col-xs-12.text-block
+ = image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36)
+ = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank')
- .form-group
- = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label'
- .col-sm-10.col-xs-12.text-block Show this command in the autocomplete list
+ .form-group
+ = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label'
+ .col-sm-10.col-xs-12.text-block Show this command in the autocomplete list
- .form-group
- = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label'
- .col-sm-10.col-xs-12.input-group
- = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly'
- .input-group-btn
- = clipboard_button(clipboard_target: '#autocomplete_description')
+ .form-group
+ = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label'
+ .col-sm-10.col-xs-12.input-group
+ = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly'
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#autocomplete_description')
- .form-group
- = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-xs-12 control-label'
- .col-sm-10.col-xs-12.input-group
- = text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly'
- .input-group-btn
- = clipboard_button(clipboard_target: '#autocomplete_usage_hint')
+ .form-group
+ = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-xs-12 control-label'
+ .col-sm-10.col-xs-12.input-group
+ = text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly'
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#autocomplete_usage_hint')
- .form-group
- = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-xs-12 control-label'
- .col-sm-10.col-xs-12.input-group
- = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control input-sm', readonly: 'readonly'
- .input-group-btn
- = clipboard_button(clipboard_target: '#descriptive_label')
+ .form-group
+ = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-xs-12 control-label'
+ .col-sm-10.col-xs-12.input-group
+ = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control input-sm', readonly: 'readonly'
+ .input-group-btn
+ = clipboard_button(clipboard_target: '#descriptive_label')
- %hr
+ %hr
- %ul.list-unstyled
- %li
- 2. Paste the
- %strong Token
- into the field below
- %li
- 3. Select the
- %strong Active
- checkbox, press
- %strong Save changes
- and start using GitLab inside Slack!
+ %ul.list-unstyled
+ %li
+ 2. Paste the
+ %strong Token
+ into the field below
+ %li
+ 3. Select the
+ %strong Active
+ checkbox, press
+ %strong Save changes
+ and start using GitLab inside Slack!
diff --git a/app/views/projects/hooks/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml
index ceabe2eab3d..ceabe2eab3d 100644
--- a/app/views/projects/hooks/_project_hook.html.haml
+++ b/app/views/projects/settings/integrations/_project_hook.html.haml
diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/show.html.haml
new file mode 100644
index 00000000000..aa38a889cdd
--- /dev/null
+++ b/app/views/projects/settings/integrations/show.html.haml
@@ -0,0 +1,3 @@
+- page_title 'Integrations'
+= render 'projects/hooks/index'
+= render 'projects/services/index'
diff --git a/app/views/projects/settings/members/show.html.haml b/app/views/projects/settings/members/show.html.haml
new file mode 100644
index 00000000000..d81ed7bb609
--- /dev/null
+++ b/app/views/projects/settings/members/show.html.haml
@@ -0,0 +1,6 @@
+- page_title "Members"
+
+= render "projects/project_members/index"
+- if can?(current_user, :admin_project, @project)
+ - if @project.allowed_to_share_with_group?
+ = render "projects/group_links/index"
diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml
index d9d392fa02f..4ee30b023ac 100644
--- a/app/views/projects/stage/_graph.html.haml
+++ b/app/views/projects/stage/_graph.html.haml
@@ -1,6 +1,6 @@
- stage = local_assigns.fetch(:stage)
- statuses = stage.statuses.latest
-- status_groups = statuses.sort_by(&:name).group_by(&:group_name)
+- status_groups = statuses.sort_by(&:sortable_name).group_by(&:group_name)
%li.stage-column
.stage-name
%a{ name: stage.name }
diff --git a/app/views/projects/stage/_in_stage_group.html.haml b/app/views/projects/stage/_in_stage_group.html.haml
index c4cb9ab50d0..9c5eb501174 100644
--- a/app/views/projects/stage/_in_stage_group.html.haml
+++ b/app/views/projects/stage/_in_stage_group.html.haml
@@ -5,9 +5,10 @@
%span.ci-status-text
= name
%span.dropdown-counter-badge= subject.size
-.dropdown-menu.grouped-pipeline-dropdown
+
+%ul.dropdown-menu.big-pipeline-graph-dropdown-menu.js-grouped-pipeline-dropdown
.arrow
- %ul
+ .scrollable-menu
- subject.each do |status|
- %li.dropdown-build
- = render 'ci/status/graph_badge', subject: status
+ %li
+ = render 'ci/status/dropdown_graph_badge', subject: status
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index 038a960bd0c..6855c463c6d 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -7,10 +7,12 @@
%th.hidden-xs
.pull-left Last commit
.last-commit.hidden-sm.pull-left
+ %i.fa.fa-angle-right
%small.light
- = clipboard_button(clipboard_text: @commit.id)
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
+ = clipboard_button(clipboard_text: @commit.id, title: "Copy commit SHA to clipboard")
= time_ago_with_tooltip(@commit.committed_date)
+ \-
= @commit.full_title
%small.commit-history-link-spacer &#124;
= link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'commit-history-link'
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index e25d6a48573..fb0efd85dcd 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -17,6 +17,13 @@
%pre.dark
:preserve
gem install gollum
+ %p
+ It is recommended to install
+ %code github-markdown
+ so that GFM features render locally:
+ %pre.dark
+ :preserve
+ gem install github-markdown
%h3 Clone your wiki
%pre.dark
diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml
index 05a63016c09..821a39d61f5 100644
--- a/app/views/search/results/_empty.html.haml
+++ b/app/views/search/results/_empty.html.haml
@@ -3,4 +3,4 @@
%h4
= icon('search')
We couldn't find any results matching
- %code #{@search_term}
+ %code= @search_term
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 07b17bc69c0..2e6adf3027c 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -2,7 +2,7 @@
%h4
= link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
%span.term.str-truncated= merge_request.title
- .pull-right #{merge_request.to_reference}
+ .pull-right= merge_request.to_reference
- if merge_request.description.present?
.description.term
= preserve do
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index c9b7bd154af..23ca6479414 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -9,7 +9,7 @@
= link_to user_snippets_path(snippet.author) do
= image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: ''
= snippet.author_name
- %span.light #{time_ago_with_tooltip(snippet.created_at)}
+ %span.light= time_ago_with_tooltip(snippet.created_at)
%h4.snippet-title
- snippet_path = reliable_snippet_path(snippet)
= link_to snippet_path do
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 027d42396b4..704d1d01a81 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -20,4 +20,4 @@
= link_to user_snippets_path(snippet_title.author) do
= image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: ''
= snippet_title.author_name
- %span.light #{time_ago_with_tooltip(snippet_title.created_at)}
+ %span.light= time_ago_with_tooltip(snippet_title.created_at)
diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml
index 000532b1c9a..94295970acf 100644
--- a/app/views/shared/_choose_group_avatar_button.html.haml
+++ b/app/views/shared/_choose_group_avatar_button.html.haml
@@ -1,4 +1,4 @@
-%a.choose-btn.btn.btn-sm.js-choose-group-avatar-button
+%button.choose-btn.btn.btn-sm.js-choose-group-avatar-button{ type: 'button' }
%i.fa.fa-paperclip
%span Choose File ...
&nbsp;
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 96b75440309..03684389742 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -19,7 +19,7 @@
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true
.input-group-btn
- = clipboard_button(clipboard_target: '#project_clone')
+ = clipboard_button(clipboard_target: '#project_clone', title: "Copy URL to clipboard")
:javascript
$('ul.clone-options-dropdown a').on('click',function(e){
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index e7cb93b17a7..f94f8ffc604 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -14,7 +14,7 @@
To prevent accidental actions we ask you to confirm your intention.
%br
Please type
- %code.js-confirm-danger-match #{phrase}
+ %code.js-confirm-danger-match= phrase
to proceed or close this modal to cancel.
.form-group
diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml
index 39294fe1a09..704893b4d5b 100644
--- a/app/views/shared/_milestones_filter.html.haml
+++ b/app/views/shared/_milestones_filter.html.haml
@@ -6,14 +6,14 @@
= link_to milestones_filter_path(state: 'opened') do
Open
- if @project
- %span.badge #{counts[:opened]}
+ %span.badge= counts[:opened]
%li{ class: milestone_class_for_state(params[:state], 'closed') }>
= link_to milestones_filter_path(state: 'closed') do
Closed
- if @project
- %span.badge #{counts[:closed]}
+ %span.badge= counts[:closed]
%li{ class: milestone_class_for_state(params[:state], 'all') }>
= link_to milestones_filter_path(state: 'all') do
All
- if @project
- %span.badge #{counts[:all]}
+ %span.badge= counts[:all]
diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml
index a43bf33751a..ed6fc76c61e 100644
--- a/app/views/shared/_no_password.html.haml
+++ b/app/views/shared/_no_password.html.haml
@@ -1,8 +1,8 @@
- if cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && current_user.require_password?
- .no-password-message.alert.alert-warning.hidden-xs
+ .no-password-message.alert.alert-warning
You won't be able to pull or push project code via #{gitlab_config.protocol.upcase} until you #{link_to 'set a password', edit_profile_password_path} on your account
- .pull-right
+ .alert-link-group
= link_to "Don't show again", profile_path(user: {hide_no_password: true}), method: :put
|
= link_to 'Remind later', '#', class: 'hide-no-password-message'
diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml
index bb5fff2d3bb..d663fa13d10 100644
--- a/app/views/shared/_no_ssh.html.haml
+++ b/app/views/shared/_no_ssh.html.haml
@@ -1,8 +1,8 @@
- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key?
- .no-ssh-key-message.alert.alert-warning.hidden-xs
+ .no-ssh-key-message.alert.alert-warning
You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', profile_keys_path, class: 'alert-link'} to your profile
- .pull-right
+ .alert-link-group
= link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link'
|
= link_to 'Remind later', '#', class: 'hide-no-ssh-message alert-link'
diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml
index f9a7aa4e29b..dd9e433491b 100644
--- a/app/views/shared/groups/_group.html.haml
+++ b/app/views/shared/groups/_group.html.haml
@@ -32,7 +32,7 @@
- if group_member
as
- %span #{group_member.human_access}
+ %span= group_member.human_access
- if group.description.present?
.description
diff --git a/app/views/shared/icons/_icon_stopwatch.svg b/app/views/shared/icons/_icon_stopwatch.svg
new file mode 100644
index 00000000000..f20de04538e
--- /dev/null
+++ b/app/views/shared/icons/_icon_stopwatch.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14" enable-background="new 0 0 12 14"><path d="m11.5 2.4l-1.3-1.1-1 1.1 1.4 1.1.9-1.1"/><path d="m6.8 2v-.5h.5v-1.5h-2.6v1.5h.5v.5c-2.9.4-5.2 2.9-5.2 6 0 3.3 2.7 6 6 6s6-2.7 6-6c0-3-2.3-5.6-5.2-6m-.8 10.5c-2.5 0-4.5-2-4.5-4.5s2-4.5 4.5-4.5 4.5 2 4.5 4.5-2 4.5-4.5 4.5"/><path d="m6.2 8.9h-.5c-.1 0-.2-.1-.2-.2v-3.5c0-.1.1-.2.2-.2h.5c.1 0 .2.1.2.2v3.5c0 .1-.1.2-.2.2"/></svg> \ No newline at end of file
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index dcc1f3ba676..0a4de709fcd 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -1,4 +1,5 @@
- form = local_assigns.fetch(:f)
+- commits = local_assigns[:commits]
- project = @target_project || @project
= form_errors(issuable)
@@ -14,7 +15,7 @@
= form.label :title, class: 'control-label'
= render 'shared/issuable/form/template_selector', issuable: issuable
- = render 'shared/issuable/form/title', issuable: issuable, form: form
+ = render 'shared/issuable/form/title', issuable: issuable, form: form, has_wip_commits: commits && commits.detect(&:work_in_progress?)
= render 'shared/issuable/form/description', issuable: issuable, form: form
@@ -67,7 +68,7 @@
- if !issuable.persisted? && !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project))
.inline.prepend-left-10
Please review the
- %strong #{link_to 'contribution guidelines', guide_url}
+ %strong= link_to('contribution guidelines', guide_url)
for this project.
- if issuable.new_record?
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
new file mode 100644
index 00000000000..55360dadbc4
--- /dev/null
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -0,0 +1,133 @@
+- type = local_assigns.fetch(:type)
+
+.issues-filters
+ .issues-details-filters.row-content-block.second-block.filtered-search-block
+ = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search]), method: :get, class: 'filter-form js-filter-form' do
+ - if params[:search].present?
+ = hidden_field_tag :search, params[:search]
+ - if @bulk_edit
+ .check-all-holder
+ = check_box_tag "check_all_issues", nil, false,
+ class: "check_all_issues left"
+ .issues-other-filters.filtered-search-container
+ .filtered-search-input-container
+ %input.form-control.filtered-search{ placeholder: 'Search or filter results...', 'data-id' => 'filtered-search', 'data-project-id' => @project.id, 'data-username-params' => @users.to_json(only: [:id, :username]) }
+ = icon('filter')
+ %button.clear-search.hidden{ type: 'button' }
+ = icon('times')
+ #js-dropdown-hint.dropdown-menu.hint-dropdown
+ %ul{ 'data-dropdown' => true }
+ %li.filter-dropdown-item{ 'data-action' => 'submit' }
+ %button.btn.btn-link
+ = icon('search')
+ %span
+ Keep typing and press Enter
+ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %li.filter-dropdown-item
+ %button.btn.btn-link
+ -# Encapsulate static class name `{{icon}}` inside #{} to bypass
+ -# haml lint's ClassAttributeWithStaticValue
+ %i.fa{ class: "#{'{{icon}}'}" }
+ %span.js-filter-hint
+ {{hint}}
+ %span.js-filter-tag.dropdown-light-content
+ {{tag}}
+ #js-dropdown-author.dropdown-menu
+ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.dropdown-user
+ %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', alt: '{{name}}\'s avatar', width: '30' }
+ .dropdown-user-details
+ %span
+ {{name}}
+ %span.dropdown-light-content
+ @{{username}}
+ #js-dropdown-assignee.dropdown-menu
+ %ul{ 'data-dropdown' => true }
+ %li.filter-dropdown-item{ 'data-value' => 'none' }
+ %button.btn.btn-link
+ No Assignee
+ %li.divider
+ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.dropdown-user
+ %img.avatar.avatar-inline{ 'data-src' => '{{avatar_url}}', alt: '{{name}}\'s avatar', width: '30' }
+ .dropdown-user-details
+ %span
+ {{name}}
+ %span.dropdown-light-content
+ @{{username}}
+ #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true }
+ %ul{ 'data-dropdown' => true }
+ %li.filter-dropdown-item{ 'data-value' => 'none' }
+ %button.btn.btn-link
+ No Milestone
+ %li.filter-dropdown-item{ 'data-value' => 'upcoming' }
+ %button.btn.btn-link
+ Upcoming
+ %li.divider
+ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value
+ {{title}}
+ #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true }
+ %ul{ 'data-dropdown' => true }
+ %li.filter-dropdown-item{ 'data-value' => 'none' }
+ %button.btn.btn-link
+ No Label
+ %li.divider
+ %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true }
+ %li.filter-dropdown-item
+ %button.btn.btn-link
+ %span.dropdown-label-box{ style: 'background: {{color}}' }
+ %span.label-title.js-data-value
+ {{title}}
+ .pull-right
+ = render 'shared/sort_dropdown'
+
+ - if @bulk_edit
+ .issues_bulk_update.hide
+ = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: 'bulk-update' do
+ .filter-item.inline
+ = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do
+ %ul
+ %li
+ %a{ href: "#", data: { id: "reopen" } } Open
+ %li
+ %a{ href: "#", data: { id: "close" } } Closed
+ .filter-item.inline
+ = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
+ placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } })
+ .filter-item.inline
+ = dropdown_tag("Milestone", options: { title: "Assign milestone", toggle_class: 'js-milestone-select js-extra-options js-filter-submit js-filter-bulk-update', filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", data: { show_no: true, field_name: "update[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), use_id: true } })
+ .filter-item.inline.labels-filter
+ = render "shared/issuable/label_dropdown", classes: ['js-filter-bulk-update', 'js-multiselect'], show_create: false, show_footer: false, extra_options: false, filter_submit: false, data_options: { persist_when_hide: "true", field_name: "update[label_ids][]", show_no: false, show_any: false, use_id: true }
+ .filter-item.inline
+ = dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]" } } ) do
+ %ul
+ %li
+ %a{ href: "#", data: { id: "subscribe" } } Subscribe
+ %li
+ %a{ href: "#", data: { id: "unsubscribe" } } Unsubscribe
+
+ = hidden_field_tag 'update[issuable_ids]', []
+ = hidden_field_tag :state_event, params[:state_event]
+ .filter-item.inline
+ = button_tag "Update #{type.to_s.humanize(capitalize: false)}", class: "btn update_selected_issues btn-save"
+
+:javascript
+ new UsersSelect();
+ new LabelsSelect();
+ new MilestoneSelect();
+ new IssueStatusSelect();
+ new SubscriptionSelect();
+
+ $(document).off('page:restore').on('page:restore', function (event) {
+ if (gl.FilteredSearchManager) {
+ new gl.FilteredSearchManager();
+ }
+ Issuable.init();
+ new gl.IssuableBulkActions({
+ prefixId: 'issue_',
+ });
+ });
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index bc57d48ae7c..ec9bcaf63dd 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,5 +1,7 @@
- todo = issuable_todo(issuable)
-%aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
+- content_for :page_specific_javascripts do
+ = page_specific_javascript_tag('issuable/issuable_bundle.js')
+%aside.right-sidebar{ class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
.issuable-sidebar
- can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.block.issuable-sidebar-header
@@ -72,7 +74,13 @@
.selectbox.hide-collapsed
= f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil
= dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }})
-
+ - if issuable.has_attribute?(:time_estimate)
+ #issuable-time-tracker.block
+ %issuable-time-tracker{ ':time_estimate' => 'issuable.time_estimate', ':time_spent' => 'issuable.total_time_spent', ':human_time_estimate' => 'issuable.human_time_estimate', ':human_time_spent' => 'issuable.human_total_time_spent', 'stopwatch-svg' => custom_icon('icon_stopwatch'), 'docs-url' => help_page_path('workflow/time_tracking.md') }
+ // Fallback while content is loading
+ .title.hide-collapsed
+ Time tracking
+ = icon('spinner spin')
- if issuable.has_attribute?(:due_date)
.block.due_date
.sidebar-collapsed-icon
@@ -97,7 +105,7 @@
remove due date
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.selectbox.hide-collapsed
- = f.hidden_field :due_date, value: issuable.due_date
+ = f.hidden_field :due_date, value: issuable.due_date.try(:strftime, 'yy-mm-dd')
.dropdown
%button.dropdown-menu-toggle.js-due-date-select{ type: 'button', data: { toggle: 'dropdown', field_name: "#{issuable.to_ability_name}[due_date]", ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable) } }
%span.dropdown-toggle-text Due date
@@ -153,15 +161,17 @@
- project_ref = cross_project_reference(@project, issuable)
.block.project-reference
.sidebar-collapsed-icon.dont-change-state
- = clipboard_button(clipboard_text: project_ref)
+ = clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left")
.cross-project-reference.hide-collapsed
%span
Reference:
%cite{ title: project_ref }
= project_ref
- = clipboard_button(clipboard_text: project_ref)
+ = clipboard_button(clipboard_text: project_ref, title: "Copy reference to clipboard", placement: "left")
:javascript
+ gl.IssuableResource = new gl.SubbableResource('#{issuable_json_path(issuable)}');
+ new gl.IssuableTimeTracking("#{escape_javascript(serialize_issuable(issuable))}");
new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}');
new LabelsSelect();
new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}');
diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml
index 83efdc7c8f7..64826d41d60 100644
--- a/app/views/shared/issuable/form/_title.html.haml
+++ b/app/views/shared/issuable/form/_title.html.haml
@@ -1,4 +1,5 @@
- issuable = local_assigns.fetch(:issuable)
+- has_wip_commits = local_assigns.fetch(:has_wip_commits)
- form = local_assigns.fetch(:form)
- no_issuable_templates = issuable_templates(issuable).empty?
- div_class = no_issuable_templates ? 'col-sm-10' : 'col-sm-7 col-lg-8'
@@ -18,6 +19,9 @@
%strong Work In Progress
merge request to be merged when it's ready.
.js-no-wip-explanation
+ - if has_wip_commits
+ It looks like you have some WIP commits in this branch.
+ %br
%a.js-toggle-wip{ href: '', tabindex: -1 }
Start the title with
%code WIP:
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index a46ba3b0605..81b5bc1de30 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -37,7 +37,6 @@
%i.clear-icon.js-clear-input
- if can_admin_member
= link_to namespace_project_group_link_path(@project.namespace, @project, group_link),
- remote: true,
method: :delete,
data: { confirm: "Are you sure you want to remove #{group.name}?" },
class: 'btn btn-remove prepend-left-10' do
diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml
index 51c195ffbcd..28935c8b598 100644
--- a/app/views/shared/milestones/_issuable.html.haml
+++ b/app/views/shared/milestones/_issuable.html.haml
@@ -16,7 +16,7 @@
= link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title
.issuable-detail
= link_to [project.namespace.becomes(Namespace), project, issuable] do
- %span.issuable-number >= issuable.to_reference
+ %span.issuable-number= issuable.to_reference
- issuable.labels.each do |label|
= link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do
diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml
index 15ff5b8a27e..31eb07ca666 100644
--- a/app/views/shared/milestones/_issuables.html.haml
+++ b/app/views/shared/milestones/_issuables.html.haml
@@ -8,7 +8,7 @@
= title
- if show_counter
.right
- = issuables.size
+ = number_with_delimiter(issuables.size)
- class_prefix = dom_class(issuables).pluralize
%ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id }
diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml
index 3200aacf542..9e6a76e1ddb 100644
--- a/app/views/shared/milestones/_milestone.html.haml
+++ b/app/views/shared/milestones/_milestone.html.haml
@@ -9,7 +9,7 @@
.pull-right.light #{milestone.percent_complete(current_user)}% complete
.row
.col-sm-6
- = link_to pluralize(milestone.issues_visible_to_user(current_user).size, 'Issue'), issues_path
+ = link_to pluralize(milestone.total_issues_count(current_user), 'Issue'), issues_path
&middot;
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
.col-sm-6= milestone_progress_bar(milestone)
diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml
index 5074afb63a1..8bbaf431536 100644
--- a/app/views/shared/tokens/_scopes_form.html.haml
+++ b/app/views/shared/tokens/_scopes_form.html.haml
@@ -5,5 +5,5 @@
- scopes.each do |scope|
%fieldset
= check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}"
- = label_tag "#{prefix}_scopes_#{scope}", scope
- %span= "(#{t(scope, scope: [:doorkeeper, :scopes])})"
+ = label_tag ("#{prefix}_scopes_#{scope}"), scope
+ %span= t(scope, scope: [:doorkeeper, :scopes])
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 5d659eb83a9..13586a5a12a 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -1,6 +1,3 @@
-- page_title "Webhooks"
-- context_title = @project ? 'project' : 'group'
-
.row.prepend-top-default
.col-lg-3
%h4.prepend-top-0
diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml
index f51599212db..b09782749f5 100644
--- a/app/views/users/calendar_activities.html.haml
+++ b/app/views/users/calendar_activities.html.haml
@@ -1,6 +1,6 @@
%h4.prepend-top-20
Contributions for
- %strong #{@calendar_date.to_s(:short)}
+ %strong= @calendar_date.to_s(:short)
- if @events.any?
%ul.bordered-list
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index fb25eed4f37..c3d33d49c1e 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -110,16 +110,16 @@
= spinner
#groups.tab-pane
- - # This tab is always loaded via AJAX
+ -# This tab is always loaded via AJAX
#contributed.tab-pane
- - # This tab is always loaded via AJAX
+ -# This tab is always loaded via AJAX
#projects.tab-pane
- - # This tab is always loaded via AJAX
+ -# This tab is always loaded via AJAX
#snippets.tab-pane
- - # This tab is always loaded via AJAX
+ -# This tab is always loaded via AJAX
.loading-status
= spinner
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index 2badd0680fb..6abbb5a5250 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -2,6 +2,13 @@ class AuthorizedProjectsWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
+ # Schedules multiple jobs and waits for them to be completed.
+ def self.bulk_perform_and_wait(args_list)
+ job_ids = bulk_perform_async(args_list)
+
+ Gitlab::JobWaiter.new(job_ids).wait
+ end
+
def self.bulk_perform_async(args_list)
Sidekiq::Client.push_bulk('class' => self, 'args' => args_list)
end
diff --git a/app/workers/build_queue_worker.rb b/app/workers/build_queue_worker.rb
new file mode 100644
index 00000000000..fa9e097e40a
--- /dev/null
+++ b/app/workers/build_queue_worker.rb
@@ -0,0 +1,10 @@
+class BuildQueueWorker
+ include Sidekiq::Worker
+ include BuildQueue
+
+ def perform(build_id)
+ Ci::Build.find_by(id: build_id).try do |build|
+ Ci::UpdateBuildQueueService.new.execute(build)
+ end
+ end
+end
diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb
index 9af9dae04f0..18b8daf4e1e 100644
--- a/app/workers/reactive_caching_worker.rb
+++ b/app/workers/reactive_caching_worker.rb
@@ -2,7 +2,7 @@ class ReactiveCachingWorker
include Sidekiq::Worker
include DedicatedSidekiqQueue
- def perform(class_name, id)
+ def perform(class_name, id, *args)
klass = begin
Kernel.const_get(class_name)
rescue NameError
@@ -10,6 +10,6 @@ class ReactiveCachingWorker
end
return unless klass
- klass.find_by(id: id).try(:exclusively_update_reactive_cache!)
+ klass.find_by(id: id).try(:exclusively_update_reactive_cache!, *args)
end
end
diff --git a/app/workers/use_key_worker.rb b/app/workers/use_key_worker.rb
new file mode 100644
index 00000000000..c9d382cc5d6
--- /dev/null
+++ b/app/workers/use_key_worker.rb
@@ -0,0 +1,13 @@
+class UseKeyWorker
+ include Sidekiq::Worker
+ include DedicatedSidekiqQueue
+
+ def perform(key_id)
+ key = Key.find(key_id)
+ key.touch(:last_used_at)
+ rescue ActiveRecord::RecordNotFound
+ Rails.logger.error("UseKeyWorker: couldn't find key with ID=#{key_id}, skipping job")
+
+ false
+ end
+end