mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-04 20:25:22 +08:00
added notification alert [fixes SCI-1566]
This commit is contained in:
parent
1af27d9361
commit
edf4443995
11 changed files with 183 additions and 28 deletions
|
@ -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
|
||||
|
|
|
@ -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 => (
|
||||
<NotificationItem key={notification.id} notification={notification} />
|
||||
);
|
||||
));
|
||||
|
||||
const items =
|
||||
this.state.notifications.length > 0
|
||||
? list
|
||||
: <CustomNavItem>
|
||||
<Spinner />
|
||||
</CustomNavItem>;
|
||||
this.state.notifications.length > 0 ? (
|
||||
list
|
||||
) : (
|
||||
<CustomNavItem>
|
||||
<Spinner />
|
||||
</CustomNavItem>
|
||||
);
|
||||
return items;
|
||||
}
|
||||
|
||||
renderNotificationStatus() {
|
||||
if (this.state.notificationsCount > 0) {
|
||||
return <StyledSpan>{this.state.notificationsCount}</StyledSpan>;
|
||||
}
|
||||
return <span />;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<StyledNavDropdown
|
||||
|
@ -89,6 +134,7 @@ class NotificationsDropdown extends Component {
|
|||
<span className="visible-xs-inline visible-sm-inline">
|
||||
<FormattedMessage id="navbar.notifications_label" />
|
||||
</span>
|
||||
{this.renderNotificationStatus()}
|
||||
</span>
|
||||
}
|
||||
onClick={this.getRecentNotifications}
|
||||
|
|
8
app/javascript/src/services/api/config.js
Normal file
8
app/javascript/src/services/api/config.js
Normal file
|
@ -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
|
||||
}
|
||||
});
|
4
app/javascript/src/services/api/endpoints.js
Normal file
4
app/javascript/src/services/api/endpoints.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
// notifications
|
||||
export const RECENT_NOTIFICATIONS_PATH = "/client_api/recent_notifications";
|
||||
export const UNREADED_NOTIFICATIONS_PATH =
|
||||
"/client_api/unreaded_notifications_number";
|
15
app/javascript/src/services/api/notifications_api.js
Normal file
15
app/javascript/src/services/api/notifications_api.js
Normal file
|
@ -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);
|
||||
};
|
|
@ -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'
|
||||
|
|
|
@ -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",
|
||||
|
|
27
spec/controllers/client_api/notifications_controller_spec.rb
Normal file
27
spec/controllers/client_api/notifications_controller_spec.rb
Normal file
|
@ -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
|
8
spec/factories/notifications.rb
Normal file
8
spec/factories/notifications.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
FactoryGirl.define do
|
||||
factory :notification do
|
||||
title '<i>Admin</i> was added as Owner to project ' \
|
||||
'<strong>Demo project - qPCR</strong> by <i>User</i>.'
|
||||
message 'Project: <a href=\"/projects/3\"> Demo project - qPCR</a>'
|
||||
type_of 'assignment'
|
||||
end
|
||||
end
|
5
spec/factories/user_notification.rb
Normal file
5
spec/factories/user_notification.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
FactoryGirl.define do
|
||||
factory :user_notification do
|
||||
checked false
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue