From 5c85595ab440ea39d52c23ba4bc741278b9bf525 Mon Sep 17 00:00:00 2001 From: aignatov-bio <47317017+aignatov-bio@users.noreply.github.com> Date: Wed, 13 Feb 2019 13:06:14 +0100 Subject: [PATCH] System Notifications - View All Page [SCI-2956 and SCI-3001] (#1482) * System notification view and notification partial (SCI 2956 and SCI 3001) --- Gemfile | 1 + Gemfile.lock | 8 +- .../javascripts/system_notifications/index.js | 39 +++++++ .../stylesheets/system_notifications.scss | 103 ++++++++++++++++++ .../system_notifications_controller.rb | 53 +++++++++ app/models/system_notification.rb | 30 +++++ app/models/user_system_notification.rb | 5 + app/views/system_notifications/_list.html.erb | 3 + .../_notification.html.erb | 22 ++++ app/views/system_notifications/index.html.erb | 34 ++++++ config/initializers/assets.rb | 1 + config/locales/en.yml | 7 +- config/routes.rb | 8 ++ db/seeds.rb | 2 +- 14 files changed, 312 insertions(+), 4 deletions(-) create mode 100644 app/assets/javascripts/system_notifications/index.js create mode 100644 app/assets/stylesheets/system_notifications.scss create mode 100644 app/controllers/system_notifications_controller.rb create mode 100644 app/views/system_notifications/_list.html.erb create mode 100644 app/views/system_notifications/_notification.html.erb create mode 100644 app/views/system_notifications/index.html.erb diff --git a/Gemfile b/Gemfile index 2dfd5cee3..312f8c36f 100644 --- a/Gemfile +++ b/Gemfile @@ -10,6 +10,7 @@ gem 'devise', '~> 4.3.0' gem 'devise_invitable' gem 'figaro' gem 'pg', '~> 0.18' +gem 'pg_search' # PostgreSQL full text search gem 'rails', '5.1.6' gem 'recaptcha', require: 'recaptcha/rails' gem 'sanitize', '~> 4.4' diff --git a/Gemfile.lock b/Gemfile.lock index 76978c064..cd2bda31a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -346,6 +346,9 @@ GEM parser (2.5.1.2) ast (~> 2.4.0) pg (0.21.0) + pg_search (2.1.4) + activerecord (>= 4.2) + activesupport (>= 4.2) phantomjs (2.1.1.0) poltergeist (1.17.0) capybara (~> 2.1) @@ -599,6 +602,7 @@ DEPENDENCIES overcommit paperclip (~> 5.3) pg (~> 0.18) + pg_search phantomjs poltergeist pry @@ -641,7 +645,7 @@ DEPENDENCIES yomu RUBY VERSION - ruby 2.4.4p296 + ruby 2.4.5p335 BUNDLED WITH - 1.17.1 + 1.17.2 diff --git a/app/assets/javascripts/system_notifications/index.js b/app/assets/javascripts/system_notifications/index.js new file mode 100644 index 000000000..2349a173f --- /dev/null +++ b/app/assets/javascripts/system_notifications/index.js @@ -0,0 +1,39 @@ +'use strict'; + +// update selected notiifcations +function SystemNotificationsMarkAsSeen() { + var WindowSize = $(window).height(); + var NotificaitonSize = 75; + var NotificationsToUpdate = []; + + _.each($('.system-notification[data-new="1"]'), function(el) { + var NotificationTopPosition = el.getBoundingClientRect().top; + if (NotificationTopPosition > 0 && NotificationTopPosition < (WindowSize - NotificaitonSize)) { + NotificationsToUpdate.push(el.dataset.systemNotificationId); + el.dataset.new = 0; + } + }); + if (NotificationsToUpdate.length > 0) { + $.post('system_notifications/mark_as_seen', { notifications: JSON.stringify(NotificationsToUpdate) }); + } +} + +function initSystemNotificationsButton() { + $('.btn-more-notifications') + .on('ajax:success', function(e, data) { + $(data.html).insertAfter($('.notifications-container .system-notification').last()); + if (data.more_url) { + $(this).attr('href', data.more_url); + } else { + $(this).remove(); + } + }); +} + +(function() { + $(window).scroll(function() { + SystemNotificationsMarkAsSeen(); + }); + initSystemNotificationsButton(); + SystemNotificationsMarkAsSeen(); +}()); diff --git a/app/assets/stylesheets/system_notifications.scss b/app/assets/stylesheets/system_notifications.scss new file mode 100644 index 000000000..500e93a3f --- /dev/null +++ b/app/assets/stylesheets/system_notifications.scss @@ -0,0 +1,103 @@ +@import "constants"; +@import "mixins"; + +// System notification partial +.system-notification { + border-bottom: 1px solid $color-gainsboro; + display: inline-block; + margin-bottom: 5px; + min-height: 70px; + padding-bottom: 5px; + position: relative; + width: 100%; + + .status-block { + float: left; + height: 0; + width: 60px; + + .status-icon { + background: $brand-success; + border-radius: 15px; + height: 30px; + line-height: 32px; + margin: 15px 20px; + text-align: center; + width: 30px; + + &.seen { + background: $color-gainsboro; + } + + i { + color: $color-white; + font-size: 18px; + margin-top: 5px; + } + } + } + + .body-block { + float: right; + width: calc(100% - 70px); + + .datetime { + font-size: 12px; + line-height: 20px; + opacity: .7; + } + + .title { + line-height: 20px; + margin: 0; + } + + .message { + line-height: 20px; + } + + } +} + + +// Global system notification +#system-notifications-index { + #search-bar-notifications { + border-bottom: 1px solid $color-gainsboro; + margin: 10px 0; + padding: 0 0 10px; + width: 100%; + + .form-group { + width: 100%; + + .input-group { + max-width: 600px; + width: 100%; + } + } + } + + .no-notification-meessage { + padding: 20px 0; + } + + .title-container { + border-bottom: 1px solid $color-gainsboro; + left: -20px; + margin: 0; + padding-bottom: 10px; + padding-left: 20px; + position: relative; + width: calc(100% + 40px); + + a { + font-size: 14px; + margin-left: 30px; + } + } + + .btn-more-notifications { + margin-top: 0; + } +} diff --git a/app/controllers/system_notifications_controller.rb b/app/controllers/system_notifications_controller.rb new file mode 100644 index 000000000..cc0b3fc50 --- /dev/null +++ b/app/controllers/system_notifications_controller.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +class SystemNotificationsController < ApplicationController + before_action :prepare_notifications, only: :index + + def index + respond_to do |format| + format.json do + render json: { + more_url: @system_notifications.fetch(:more_notifications_url), + html: render_to_string( + partial: 'list.html.erb', locals: @system_notifications + ) + } + end + format.html + end + end + + # Update seen_at parameter for system notifications + def mark_as_seen + notifications = JSON.parse(params[:notifications]) + current_user.user_system_notifications.mark_as_seen(notifications) + render json: { result: 'ok' } + rescue StandardError + render json: { result: 'failed' } + end + + private + + def prepare_notifications + page = (params[:page] || 1).to_i + query = params[:search_queue] + per_page = Constants::ACTIVITY_AND_NOTIF_SEARCH_LIMIT + notifications = SystemNotification.last_notifications(current_user, query) + .page(page) + .per(per_page) + + unless notifications.blank? || notifications.last_page? + more_url = url_for( + system_notifications_url( + format: :json, + page: page + 1, + search_queue: query + ) + ) + end + @system_notifications = { + notifications: notifications, + more_notifications_url: more_url + } + end +end diff --git a/app/models/system_notification.rb b/app/models/system_notification.rb index 0330901d6..cb1c48cdc 100644 --- a/app/models/system_notification.rb +++ b/app/models/system_notification.rb @@ -1,10 +1,40 @@ # frozen_string_literal: true class SystemNotification < ApplicationRecord + include PgSearch + # Full text postgreSQL search configuration + pg_search_scope :search_notifications, against: %i(title description), + using: { + tsearch: { + dictionary: 'english' + } + } + # ignoring: :accents + has_many :user_system_notifications has_many :users, through: :user_system_notifications validates :title, :modal_title, :modal_body, :description, :source_created_at, :source_id, :last_time_changed_at, presence: true + + validates :title, :description, + length: { maximum: Constants::NAME_MAX_LENGTH } + + def self.last_notifications( + user, + query = nil + ) + notifications = order(last_time_changed_at: :DESC) + notifications = notifications.search_notifications(query) if query.present? + notifications.joins(:user_system_notifications) + .where('user_system_notifications.user_id = ?', user.id) + .select( + 'system_notifications.id', + :title, + :description, + :last_time_changed_at, + 'user_system_notifications.seen_at' + ) + end end diff --git a/app/models/user_system_notification.rb b/app/models/user_system_notification.rb index 5026efb53..ea37445d1 100644 --- a/app/models/user_system_notification.rb +++ b/app/models/user_system_notification.rb @@ -3,4 +3,9 @@ class UserSystemNotification < ApplicationRecord belongs_to :user belongs_to :system_notification + + def self.mark_as_seen(notifications) + where(system_notification_id: notifications) + .update_all(seen_at: Time.now) + end end diff --git a/app/views/system_notifications/_list.html.erb b/app/views/system_notifications/_list.html.erb new file mode 100644 index 000000000..ae733da22 --- /dev/null +++ b/app/views/system_notifications/_list.html.erb @@ -0,0 +1,3 @@ +<% notifications.each do |notification| %> + <%= render partial: 'notification.html.erb', locals: { notification: notification} %> +<% end %> diff --git a/app/views/system_notifications/_notification.html.erb b/app/views/system_notifications/_notification.html.erb new file mode 100644 index 000000000..4be5aab87 --- /dev/null +++ b/app/views/system_notifications/_notification.html.erb @@ -0,0 +1,22 @@ +
+
+
"> + +
+
+
+
+ <%= l(notification.last_time_changed_at, format: :full) %> +
+
+ <%= notification.title %> +
+
+ <%= notification.description %> +
+
+
diff --git a/app/views/system_notifications/index.html.erb b/app/views/system_notifications/index.html.erb new file mode 100644 index 000000000..88e0648c4 --- /dev/null +++ b/app/views/system_notifications/index.html.erb @@ -0,0 +1,34 @@ +<% provide(:head_title, t("system_notifications.index.whats_new")) %> +
+

+ <%= t("system_notifications.index.whats_new") %> + <%= link_to t("system_notifications.index.settings"), nil %> +

+ <%= form_tag system_notifications_path, method: :get, id: "search-bar-notifications", class: "navbar-form navbar-left", role: "search" do %> +
+
+ " value="<%= params[:search_queue] %>" /> + + + +
+
+ <% end %> +
+ <%= render partial: "list", locals: { notifications: @system_notifications[:notifications] } %> +
+
+ <% if @system_notifications[:more_notifications_url] && @system_notifications[:notifications].present? %> + + <%= t("system_notifications.index.more_notifications") %> + <% else %> + <%= t("system_notifications.index.no_notifications") %> + <% end %> +
+
+ +<%= javascript_include_tag "system_notifications/index.js" %> diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index d610a3859..2ed794736 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -77,6 +77,7 @@ Rails.application.config.assets.precompile += %w(assets.js) Rails.application.config.assets.precompile += %w(comments.js) Rails.application.config.assets.precompile += %w(projects/show.js) Rails.application.config.assets.precompile += %w(notifications.js) +Rails.application.config.assets.precompile += %w(system_notifications/index.js) Rails.application.config.assets.precompile += %w(users/invite_users_modal.js) # Rails.application.config.assets.precompile += %w(samples/sample_types_groups.js) Rails.application.config.assets.precompile += %w(highlightjs-github-theme.css) diff --git a/config/locales/en.yml b/config/locales/en.yml index 6c9ceeb3b..917f327af 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1291,7 +1291,12 @@ en: color_label: "Sample group color" create: success_flash: "Successfully added sample group %{sample_group} to team %{team}." - + system_notifications: + index: + whats_new: "What's new" + more_notifications: "More system notifications" + no_notifications: "No system notifications" + settings: "Settings" activities: index: today: "Today" diff --git a/config/routes.rb b/config/routes.rb index b199adc69..5ddfba24b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + use_doorkeeper do skip_controllers :applications, :authorized_applications, :token_info end @@ -424,6 +425,13 @@ Rails.application.routes.draw do end end + # System notifications routes + resources :system_notifications, only: [:index] do + collection do + post 'mark_as_seen' + end + end + # tinyMCE image uploader endpoint post '/tinymce_assets', to: 'tiny_mce_assets#create', as: :tiny_mce_assets diff --git a/db/seeds.rb b/db/seeds.rb index 5f9f93555..4e81d981d 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -23,4 +23,4 @@ if User.count.zero? [], Extends::INITIAL_USER_OPTIONS ) -end +end \ No newline at end of file