From edf44439958304b3ef66e1f6b68f7895422745ca Mon Sep 17 00:00:00 2001 From: zmagod Date: Mon, 2 Oct 2017 14:51:54 +0200 Subject: [PATCH 1/4] added notification alert [fixes SCI-1566] --- .../client_api/notifications_controller.rb | 27 ++++--- .../components/NotificationsDropdown.jsx | 78 +++++++++++++++---- app/javascript/src/services/api/config.js | 8 ++ app/javascript/src/services/api/endpoints.js | 4 + .../src/services/api/notifications_api.js | 15 ++++ config/routes.rb | 2 + package.json | 2 +- .../notifications_controller_spec.rb | 27 +++++++ spec/factories/notifications.rb | 8 ++ spec/factories/user_notification.rb | 5 ++ spec/models/user_notification_spec.rb | 35 +++++++++ 11 files changed, 183 insertions(+), 28 deletions(-) create mode 100644 app/javascript/src/services/api/config.js create mode 100644 app/javascript/src/services/api/endpoints.js create mode 100644 app/javascript/src/services/api/notifications_api.js create mode 100644 spec/controllers/client_api/notifications_controller_spec.rb create mode 100644 spec/factories/notifications.rb create mode 100644 spec/factories/user_notification.rb diff --git a/app/controllers/client_api/notifications_controller.rb b/app/controllers/client_api/notifications_controller.rb index 3985813ed..4a520ba31 100644 --- a/app/controllers/client_api/notifications_controller.rb +++ b/app/controllers/client_api/notifications_controller.rb @@ -1,23 +1,28 @@ module ClientApi class NotificationsController < ApplicationController - before_action :last_notifications, only: :recent_notifications - def recent_notifications respond_to do |format| format.json do render template: '/client_api/notifications/index', status: :ok, - locals: { notifications: @recent_notifications } + locals: { + notifications: + UserNotification.recent_notifications(current_user) + } + end + end + # clean the unseen notifications + UserNotification.seen_by_user(current_user) + end + + def unreaded_notifications_number + respond_to do |format| + format.json do + render json: { + count: UserNotification.unseen_notification_count(current_user) + }, status: :ok end end end - - private - - def last_notifications - @recent_notifications = - UserNotification.recent_notifications(current_user) - UserNotification.seen_by_user(current_user) - end end end diff --git a/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx b/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx index 334b8eca8..07671ee90 100644 --- a/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx +++ b/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx @@ -1,10 +1,12 @@ import React, { Component } from "react"; import { NavDropdown } from "react-bootstrap"; import { FormattedMessage } from "react-intl"; -import axios from "axios"; import styled from "styled-components"; -import { RECENT_NOTIFICATIONS_PATH } from "../../../config/routes"; +import { + getRecentNotifications, + getUnreadedNotificationsNumber +} from "../../../services/api/notifications_api"; import { MAIN_COLOR_BLUE, WILD_SAND_COLOR, @@ -21,7 +23,9 @@ const StyledListHeader = styled(CustomNavItem)` font-weight: bold; padding: 8px; - & a, a:hover, a:active { + & a, + a:hover, + a:active { color: ${WILD_SAND_COLOR}; } `; @@ -44,40 +48,81 @@ const StyledNavDropdown = styled(NavDropdown)` } `; +const StyledSpan = styled.span` + background-color: #37a0d9; + border-radius: 5px; + color: #f5f5f5; + font-size: 11px; + font-weight: bold; + margin-left: 12px; + padding: 1px 6px; + right: 19px; + top: 3px; + position: relative; +`; + class NotificationsDropdown extends Component { constructor(props) { super(props); - this.state = { notifications: [] }; + this.state = { + notifications: [], + notificationsCount: 0 + }; this.getRecentNotifications = this.getRecentNotifications.bind(this); this.renderNotifications = this.renderNotifications.bind(this); + this.renderNotificationStatus = this.renderNotificationStatus.bind(this); + this.loadStatus = this.loadStatus.bind(this); + } + + componentWillMount() { + this.loadStatus(); + } + + componentDidMount() { + const minutes = 60 * 1000; + setInterval(this.loadStatus, minutes); } getRecentNotifications(e) { e.preventDefault(); - axios - .get(RECENT_NOTIFICATIONS_PATH, { withCredentials: true }) - .then(({ data }) => { - this.setState({ notifications: data }); - }) + getRecentNotifications() + .then(response => + this.setState({ notifications: response, notificationsCount: 0 }) + ) .catch(error => { console.log("get Notifications Error: ", error); // TODO change this }); } + loadStatus() { + getUnreadedNotificationsNumber().then(response => { + this.setState({ notificationsCount: parseInt(response.count, 10) }); + }); + } + renderNotifications() { - const list = this.state.notifications.map(notification => + const list = this.state.notifications.map(notification => ( - ); + )); const items = - this.state.notifications.length > 0 - ? list - : - - ; + this.state.notifications.length > 0 ? ( + list + ) : ( + + + + ); return items; } + renderNotificationStatus() { + if (this.state.notificationsCount > 0) { + return {this.state.notificationsCount}; + } + return ; + } + render() { return ( + {this.renderNotificationStatus()} } onClick={this.getRecentNotifications} diff --git a/app/javascript/src/services/api/config.js b/app/javascript/src/services/api/config.js new file mode 100644 index 000000000..86f47cb97 --- /dev/null +++ b/app/javascript/src/services/api/config.js @@ -0,0 +1,8 @@ +import axios from "axios"; + +export const axiosInstance = axios.create({ + withCredentials: true, + headers: { + "X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content + } +}); diff --git a/app/javascript/src/services/api/endpoints.js b/app/javascript/src/services/api/endpoints.js new file mode 100644 index 000000000..547e196f3 --- /dev/null +++ b/app/javascript/src/services/api/endpoints.js @@ -0,0 +1,4 @@ +// notifications +export const RECENT_NOTIFICATIONS_PATH = "/client_api/recent_notifications"; +export const UNREADED_NOTIFICATIONS_PATH = + "/client_api/unreaded_notifications_number"; diff --git a/app/javascript/src/services/api/notifications_api.js b/app/javascript/src/services/api/notifications_api.js new file mode 100644 index 000000000..0bad62634 --- /dev/null +++ b/app/javascript/src/services/api/notifications_api.js @@ -0,0 +1,15 @@ +import { axiosInstance } from "./config"; +import { + RECENT_NOTIFICATIONS_PATH, + UNREADED_NOTIFICATIONS_PATH +} from "./endpoints"; + +export const getRecentNotifications = () => { + return axiosInstance.get(RECENT_NOTIFICATIONS_PATH).then(({ data }) => data); +}; + +export const getUnreadedNotificationsNumber = () => { + return axiosInstance + .get(UNREADED_NOTIFICATIONS_PATH) + .then(({ data }) => data); +}; diff --git a/config/routes.rb b/config/routes.rb index 81f0d7304..6fbf50603 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,6 +27,8 @@ Rails.application.routes.draw do end # notifications get '/recent_notifications', to: 'notifications#recent_notifications' + get '/unreaded_notifications_number', + to: 'notifications#unreaded_notifications_number' # users get '/current_user_info', to: 'users/users#current_user_info' diff --git a/package.json b/package.json index dd89a39d0..6a3085177 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "react-bootstrap-table": "^4.0.0", "react-bootstrap-timezone-picker": "^1.0.11", "react-data-grid": "^2.0.2", - "react-tagsinput": "^3.17.0", "react-dom": "^15.6.1", "react-intl": "^2.3.0", "react-intl-redux": "^0.6.0", @@ -77,6 +76,7 @@ "react-router-bootstrap": "^0.24.2", "react-router-dom": "^4.1.2", "react-router-prop-types": "^0.0.1", + "react-tagsinput": "^3.17.0", "react-timezone": "^0.2.0", "redux": "^3.7.2", "redux-thunk": "^2.2.0", diff --git a/spec/controllers/client_api/notifications_controller_spec.rb b/spec/controllers/client_api/notifications_controller_spec.rb new file mode 100644 index 000000000..80c98d8a7 --- /dev/null +++ b/spec/controllers/client_api/notifications_controller_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +describe ClientApi::NotificationsController, type: :controller do + login_user + let(:notification) { create :notification } + let(:user_notification) do + create :user_notification, + user: User.first, + notification: notification + end + + describe '#recent_notifications' do + it 'returns a list of notifications' do + get :recent_notifications, format: :json + expect(response).to be_success + expect(response).to render_template('client_api/notifications/index') + end + end + + describe '#unreaded_notifications_number' do + it 'returns a number of unreaded notifications' do + get :unreaded_notifications_number, format: :json + expect(response).to be_success + expect(response.body).to include('count') + end + end +end diff --git a/spec/factories/notifications.rb b/spec/factories/notifications.rb new file mode 100644 index 000000000..f92723cf6 --- /dev/null +++ b/spec/factories/notifications.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :notification do + title 'Admin was added as Owner to project ' \ + 'Demo project - qPCR by User.' + message 'Project: Demo project - qPCR' + type_of 'assignment' + end +end diff --git a/spec/factories/user_notification.rb b/spec/factories/user_notification.rb new file mode 100644 index 000000000..9341a28f2 --- /dev/null +++ b/spec/factories/user_notification.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :user_notification do + checked false + end +end diff --git a/spec/models/user_notification_spec.rb b/spec/models/user_notification_spec.rb index 70eb9445f..a7ba560f2 100644 --- a/spec/models/user_notification_spec.rb +++ b/spec/models/user_notification_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' describe UserNotification, type: :model do + let(:user) { create :user } + it 'should be of class UserNotification' do expect(subject.class).to eq UserNotification end @@ -17,4 +19,37 @@ describe UserNotification, type: :model do it { should belong_to :user } it { should belong_to :notification } end + + describe '#unseen_notification_count ' do + let(:notifcation) { create :notification } + it 'returns a number of unseen notifications' do + create :user_notification, user: user, notification: notifcation + expect(UserNotification.unseen_notification_count(user)).to eq 1 + end + end + + describe '#recent_notifications' do + let(:notifcation_one) { create :notification } + let(:notifcation_two) { create :notification } + + it 'returns a list of notifications ordered by created_at DESC' do + create :user_notification, user: user, notification: notifcation_one + create :user_notification, user: user, notification: notifcation_two + notifications = UserNotification.recent_notifications(user) + expect(notifications).to eq [notifcation_two, notifcation_one] + end + end + + describe '#seen_by_user' do + let!(:notification) { create :notification } + let!(:user_notification_one) do + create :user_notification, user: user, notification: notification + end + + it 'set the check status to false' do + expect { + UserNotification.seen_by_user(user) + }.to change { user_notification_one.reload.checked }.from(false).to(true) + end + end end From 182e3dcc37a5ba91941f638ee618dba5d98dcabf Mon Sep 17 00:00:00 2001 From: zmagod Date: Mon, 2 Oct 2017 14:56:49 +0200 Subject: [PATCH 2/4] added colors constants --- .../Navigation/components/NotificationsDropdown.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx b/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx index 07671ee90..ec2147fc8 100644 --- a/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx +++ b/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx @@ -49,9 +49,9 @@ const StyledNavDropdown = styled(NavDropdown)` `; const StyledSpan = styled.span` - background-color: #37a0d9; + background-color: ${MAIN_COLOR_BLUE}; border-radius: 5px; - color: #f5f5f5; + color: ${WILD_SAND_COLOR}; font-size: 11px; font-weight: bold; margin-left: 12px; From 5a31df7204a6ca44ddfbfb5c59562c63ab9aa177 Mon Sep 17 00:00:00 2001 From: zmagod Date: Fri, 6 Oct 2017 10:45:22 +0200 Subject: [PATCH 3/4] adds @Ducz0r suggestions --- .../client_api/notifications_controller.rb | 2 +- .../Navigation/components/NotificationsDropdown.jsx | 12 ++++++++---- app/javascript/src/services/api/endpoints.js | 2 +- app/javascript/src/services/api/notifications_api.js | 2 +- config/routes.rb | 4 ++-- .../client_api/notifications_controller_spec.rb | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/controllers/client_api/notifications_controller.rb b/app/controllers/client_api/notifications_controller.rb index 4a520ba31..07bfd7230 100644 --- a/app/controllers/client_api/notifications_controller.rb +++ b/app/controllers/client_api/notifications_controller.rb @@ -15,7 +15,7 @@ module ClientApi UserNotification.seen_by_user(current_user) end - def unreaded_notifications_number + def unread_notifications_count respond_to do |format| format.json do render json: { diff --git a/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx b/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx index ec2147fc8..d63e1f47c 100644 --- a/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx +++ b/app/javascript/src/components/Navigation/components/NotificationsDropdown.jsx @@ -1,17 +1,21 @@ import React, { Component } from "react"; +import { Link } from "react-router-dom"; import { NavDropdown } from "react-bootstrap"; import { FormattedMessage } from "react-intl"; import styled from "styled-components"; import { getRecentNotifications, - getUnreadedNotificationsNumber + getUnreadNotificationsCount } from "../../../services/api/notifications_api"; import { MAIN_COLOR_BLUE, WILD_SAND_COLOR, MYSTIC_COLOR } from "../../../config/constants/colors"; +import { + SETTINGS_ACCOUNT_PREFERENCES +} from "../../../config/routes" import NotificationItem from "./NotificationItem"; import Spinner from "../../Spinner"; @@ -95,7 +99,7 @@ class NotificationsDropdown extends Component { } loadStatus() { - getUnreadedNotificationsNumber().then(response => { + getUnreadNotificationsCount().then(response => { this.setState({ notificationsCount: parseInt(response.count, 10) }); }); } @@ -144,9 +148,9 @@ class NotificationsDropdown extends Component { - + - + {this.renderNotifications()} diff --git a/app/javascript/src/services/api/endpoints.js b/app/javascript/src/services/api/endpoints.js index 547e196f3..2fb105cec 100644 --- a/app/javascript/src/services/api/endpoints.js +++ b/app/javascript/src/services/api/endpoints.js @@ -1,4 +1,4 @@ // notifications export const RECENT_NOTIFICATIONS_PATH = "/client_api/recent_notifications"; export const UNREADED_NOTIFICATIONS_PATH = - "/client_api/unreaded_notifications_number"; + "/client_api/unread_notifications_count"; diff --git a/app/javascript/src/services/api/notifications_api.js b/app/javascript/src/services/api/notifications_api.js index 0bad62634..a724a44f0 100644 --- a/app/javascript/src/services/api/notifications_api.js +++ b/app/javascript/src/services/api/notifications_api.js @@ -8,7 +8,7 @@ export const getRecentNotifications = () => { return axiosInstance.get(RECENT_NOTIFICATIONS_PATH).then(({ data }) => data); }; -export const getUnreadedNotificationsNumber = () => { +export const getUnreadNotificationsCount = () => { return axiosInstance .get(UNREADED_NOTIFICATIONS_PATH) .then(({ data }) => data); diff --git a/config/routes.rb b/config/routes.rb index 6fbf50603..6062c8012 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -27,8 +27,8 @@ Rails.application.routes.draw do end # notifications get '/recent_notifications', to: 'notifications#recent_notifications' - get '/unreaded_notifications_number', - to: 'notifications#unreaded_notifications_number' + get '/unread_notifications_count', + to: 'notifications#unread_notifications_count' # users get '/current_user_info', to: 'users/users#current_user_info' diff --git a/spec/controllers/client_api/notifications_controller_spec.rb b/spec/controllers/client_api/notifications_controller_spec.rb index 80c98d8a7..3801f1d3f 100644 --- a/spec/controllers/client_api/notifications_controller_spec.rb +++ b/spec/controllers/client_api/notifications_controller_spec.rb @@ -19,7 +19,7 @@ describe ClientApi::NotificationsController, type: :controller do describe '#unreaded_notifications_number' do it 'returns a number of unreaded notifications' do - get :unreaded_notifications_number, format: :json + get :unread_notifications_count, format: :json expect(response).to be_success expect(response.body).to include('count') end From f370d170df717fa7168f547b6a2012c993c16743 Mon Sep 17 00:00:00 2001 From: zmagod Date: Mon, 9 Oct 2017 09:27:25 +0200 Subject: [PATCH 4/4] fix package.json --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index b71c0a2d6..85531bb67 100644 --- a/package.json +++ b/package.json @@ -68,11 +68,8 @@ "react-bootstrap-table": "^4.0.0", "react-bootstrap-timezone-picker": "^1.0.11", "react-data-grid": "^2.0.2", -<<<<<<< HEAD -======= "react-tagsinput": "^3.17.0", "react-transition-group": "^2.2.0", ->>>>>>> fcea55c2a102470bd4520ea0ac1ef771710c17d8 "react-dom": "^15.6.1", "react-intl": "^2.3.0", "react-intl-redux": "^0.6.0",