diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index a3185f876408aaf2a85d4c69d9886584c509857e..61edcf8fdd7617f3b637ddced43f91060987cf80 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -74,6 +74,7 @@ class Dispatcher when 'projects:show' shortcut_handler = new ShortcutsNavigation() + new NotificationsForm() new TreeView() if $('#tree-slider').length when 'groups:activity' new Activities() diff --git a/app/assets/javascripts/notifications_form.js.coffee b/app/assets/javascripts/notifications_form.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..cfe8e133b66557cc3b3e35132a826599b6e19d07 --- /dev/null +++ b/app/assets/javascripts/notifications_form.js.coffee @@ -0,0 +1,49 @@ +class @NotificationsForm + constructor: -> + @form = $('.custom-notifications-form') + + @removeEventListeners() + @initEventListeners() + + removeEventListeners: -> + $(document).off 'change', '.js-custom-notification-event' + + initEventListeners: -> + $(document).on 'change', '.js-custom-notification-event', @toggleCheckbox + + toggleCheckbox: (e) => + $checkbox = $(e.currentTarget) + $parent = $checkbox.closest('.checkbox') + + @saveEvent($checkbox, $parent) + + showCheckboxLoadingSpinner: ($parent) -> + $parent + .addClass 'is-loading' + .find '.custom-notification-event-loading' + .removeClass 'fa-check' + .addClass 'fa-spin fa-spinner' + .removeClass 'is-done' + + saveEvent: ($checkbox, $parent) -> + $.ajax( + url: @form.attr('action') + method: 'patch' + dataType: 'json' + data: @form.serialize() + beforeSend: => + @showCheckboxLoadingSpinner($parent) + ).done (data) -> + $checkbox.enable() + + if data.saved + $parent + .find '.custom-notification-event-loading' + .toggleClass 'fa-spin fa-spinner fa-check is-done' + + setTimeout(-> + $parent + .removeClass 'is-loading' + .find '.custom-notification-event-loading' + .toggleClass 'fa-spin fa-spinner fa-check is-done' + , 2000) diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 07be85a32a5cfa43b813626512de3ab8646cf77c..236f0899147b7156caba7baa6da3ce582d94c5a3 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -34,21 +34,26 @@ class @Project $(@).parents('.no-password-message').remove() e.preventDefault() - $('.update-notification').on 'click', (e) -> - e.preventDefault() - notification_level = $(@).data 'notification-level' - label = $(@).data 'notification-title' - $('#notification_setting_level').val(notification_level) - $('#notification-form').submit() - $('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>") - $(@).parents('ul').find('li.active').removeClass 'active' - $(@).parent().addClass 'active' - - $('#notification-form').on 'ajax:success', (e, data) -> - if data.saved - new Flash("Notification settings saved", "notice") - else - new Flash("Failed to save new settings", "alert") + $(document) + .off 'click', '.update-notification' + .on 'click', '.update-notification', (e) -> + e.preventDefault() + notificationLevel = $(@).data 'notification-level' + label = $(@).data 'notification-title' + $('.js-notification-loading').toggleClass 'fa-bell fa-spin fa-spinner' + $('#notification_setting_level').val(notificationLevel) + $('#notification-form').submit() + + $(document) + .off 'ajax:success', '#notification-form' + .on 'ajax:success', '#notification-form', (e, data) -> + if data.saved + new Flash('Notification settings saved', 'notice') + $('.js-notification-toggle-btns') + .closest('.notification-dropdown') + .replaceWith(data.html) + else + new Flash('Failed to save new settings', 'alert') @projectSelectDropdown() diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index edef336481d46e91a89da58f635e8651cc0dc0a0..448ece1bff779e71bc2a6fbabd25020d157dfb71 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -125,11 +125,6 @@ } } - .btn-group:not(:first-child):not(:last-child) > .btn { - border-top-right-radius: 3px; - border-bottom-right-radius: 3px; - } - form { margin-left: 10px; } @@ -594,3 +589,20 @@ pre.light-well { } } } + +.custom-notifications-form { + .is-loading { + .custom-notification-event-loading { + display: inline-block; + } + } +} + +.custom-notification-event-loading { + display: none; + margin-left: 5px; + + &.is-done { + color: $gl-text-green; + } +} diff --git a/app/controllers/projects/notification_settings_controller.rb b/app/controllers/projects/notification_settings_controller.rb index 7d81cc03c73f6a91bc322ca5221654ed163a03cb..13b38501ae4c117997786f10633ed446e6e7c476 100644 --- a/app/controllers/projects/notification_settings_controller.rb +++ b/app/controllers/projects/notification_settings_controller.rb @@ -2,10 +2,24 @@ class Projects::NotificationSettingsController < Projects::ApplicationController before_action :authenticate_user! def update - notification_setting = current_user.notification_settings_for(project) - saved = notification_setting.update_attributes(notification_setting_params) + @notification_setting = current_user.notification_settings_for(project) - render json: { saved: saved } + if params[:custom_events].nil? + saved = @notification_setting.update_attributes(notification_setting_params) + else + events = params[:events] || {} + + NotificationSetting::EMAIL_EVENTS.each do |event| + @notification_setting.events[event] = events[event] + end + + saved = @notification_setting.save + end + + render json: { + html: view_to_html_string("projects/buttons/_notifications", locals: { project: @project, notification_setting: @notification_setting }), + saved: saved, + } end private diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 57c3d1b0a65bb08284fcde02c4255721c2afb5ec..0ee1c83cd11356f2259509de8f66276045ab73b9 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -29,9 +29,10 @@ .project-clone-holder = render "shared/clone_panel" - .project-repo-buttons.btn-group.project-right-buttons - = render "projects/buttons/download" - = render 'projects/buttons/dropdown' + .project-repo-buttons.project-right-buttons + .btn-group + = render "projects/buttons/download" + = render 'projects/buttons/dropdown' = render 'projects/buttons/notifications' :javascript diff --git a/app/views/projects/buttons/_notifications.html.haml b/app/views/projects/buttons/_notifications.html.haml index 1d05da50581fdd72fd4a0b6e4a4a89e571ff4787..47a12e6f8cb19753fd6e9e811e01ad5492aaf615 100644 --- a/app/views/projects/buttons/_notifications.html.haml +++ b/app/views/projects/buttons/_notifications.html.haml @@ -1,11 +1,21 @@ - if @notification_setting - = form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, remote: true, html: { class: 'inline', id: 'notification-form' } do |f| - = f.hidden_field :level - .dropdown - %a.dropdown-new.btn.notifications-btn#notifications-button{href: '#', "data-toggle" => "dropdown"} - = icon('bell') - = notification_title(@notification_setting.level) - = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown - - NotificationSetting.levels.each do |level| - = notification_list_item(level.first, @notification_setting) + .dropdown.notification-dropdown.pull-right + = form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, remote: true, html: { class: "inline", id: "notification-form" } do |f| + = f.hidden_field :level + .js-notification-toggle-btns + - if @notification_setting.level != "custom" + %button.dropdown-new.btn.btn-default.notifications-btn#notifications-button{ type: "button", data: { toggle: "dropdown", target: ".notification-dropdown" } } + = icon("bell", class: "js-notification-loading") + = notification_title(@notification_setting.level) + = icon("caret-down") + - else + .btn-group + %button.dropdown-new.btn.btn-default.notifications-btn#notifications-button{ type: "button", data: { toggle: "modal", target: "#custom-notifications-modal" } } + = icon("bell", class: "js-notification-loading") + = notification_title(@notification_setting.level) + %button.btn.btn-danger.dropdown-toggle{ data: { toggle: "dropdown", target: ".notification-dropdown" } } + %span.caret + .sr-only Toggle dropdown + = render "shared/projects/notification_dropdown" + = content_for :scripts_body do + = render "shared/projects/custom_notifications" diff --git a/app/views/shared/projects/_custom_notifications.html.haml b/app/views/shared/projects/_custom_notifications.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..8569c523ef47254356420c963288e5acf99fd523 --- /dev/null +++ b/app/views/shared/projects/_custom_notifications.html.haml @@ -0,0 +1,27 @@ +#custom-notifications-modal.modal.fade{ tabindex: "-1", role: "dialog", aria: { labelledby: "custom-notifications-title" } } + .modal-dialog + .modal-content + .modal-header + %button.close{ type: "button", data: { dismiss: "modal" }, aria: { label: "close" } } + %span{ aria: { hidden: "true" } } × + %h4#custom-notifications-title.modal-title + Custom notification events + .modal-body + .container-fluid + = form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, html: { class: "custom-notifications-form" } do |f| + = hidden_field_tag "custom_events", "true" + = f.hidden_field :level + .row + .col-lg-3 + %h4.prepend-top-0 + Notification events + .col-lg-9 + - NotificationSetting::EMAIL_EVENTS.each do |event, index| + = index + .form-group + .checkbox{ class: ("prepend-top-0" if index == 0) } + %label{ for: "events_#{event}" } + = check_box_tag "events[#{event}]", true, @notification_setting.events[event], id: "events_#{event}", class: "js-custom-notification-event" + %strong + = event.to_s.humanize + = icon("spinner spin", class: "custom-notification-event-loading") diff --git a/app/views/shared/projects/_notification_dropdown.html.haml b/app/views/shared/projects/_notification_dropdown.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..438fde7771dc3c2ff1740f4fd1b12b620ba81b33 --- /dev/null +++ b/app/views/shared/projects/_notification_dropdown.html.haml @@ -0,0 +1,9 @@ +%ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown + - NotificationSetting.levels.each do |level| + - if level.first != "custom" + = notification_list_item(level.first, @notification_setting) + - if @notification_setting.level != "custom" + %li.divider + %li + %a.update-notification{ href: "#", role: "button", data: { toggle: "modal", target: "#custom-notifications-modal", notification_level: "custom", notification_title: "Custom" } } + Custom diff --git a/db/schema.rb b/db/schema.rb index c7eeaadb1315394476896700c9dc9f76e9a88e69..6d648ec0692156a9904cc2974b8b95574ef3bd04 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160528043124) do +ActiveRecord::Schema.define(version: 20160531183627) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -43,46 +43,47 @@ t.datetime "created_at" t.datetime "updated_at" t.string "home_page_url" - t.integer "default_branch_protection", default: 2 + t.integer "default_branch_protection", default: 2 t.text "restricted_visibility_levels" - t.boolean "version_check_enabled", default: true - t.integer "max_attachment_size", default: 10, null: false + t.boolean "version_check_enabled", default: true + t.integer "max_attachment_size", default: 10, null: false t.integer "default_project_visibility" t.integer "default_snippet_visibility" t.text "restricted_signup_domains" - t.boolean "user_oauth_applications", default: true + t.boolean "user_oauth_applications", default: true t.string "after_sign_out_path" - t.integer "session_expire_delay", default: 10080, null: false + t.integer "session_expire_delay", default: 10080, null: false t.text "import_sources" t.text "help_page_text" t.string "admin_notification_email" - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 - t.boolean "recaptcha_enabled", default: false + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false t.string "recaptcha_site_key" t.string "recaptcha_private_key" - t.integer "metrics_port", default: 8089 - t.boolean "akismet_enabled", default: false + t.integer "metrics_port", default: 8089 + t.boolean "akismet_enabled", default: false t.string "akismet_api_key" - t.integer "metrics_sample_interval", default: 15 - t.boolean "sentry_enabled", default: false + t.integer "metrics_sample_interval", default: 15 + t.boolean "sentry_enabled", default: false t.string "sentry_dsn" - t.boolean "email_author_in_body", default: false + t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" - t.boolean "repository_checks_enabled", default: false + t.boolean "repository_checks_enabled", default: false t.text "shared_runners_text" - t.integer "metrics_packet_size", default: 1 + t.integer "metrics_packet_size", default: 1 t.text "disabled_oauth_sign_in_sources" t.string "health_check_access_token" - t.boolean "send_user_confirmation_email", default: false + t.boolean "send_user_confirmation_email", default: false + t.integer "container_registry_token_expire_delay", default: 5 end create_table "audit_events", force: :cascade do |t| @@ -639,6 +640,7 @@ t.integer "updated_by_id" t.boolean "is_award", default: false, null: false t.string "type" + t.string "system_type" end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -661,6 +663,7 @@ t.integer "level", default: 0, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "events" end add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index a1785311c2b8aef2cc5855e6513425205ba5abaf..98b57e5cbfbe21766426c3854520d6544a487bf0 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -126,7 +126,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I click notifications drop down button' do - click_link 'notifications-button' + first('.notifications-btn').click end step 'I choose Mention setting' do