Merged in VRFS-5004-revamp_notification (pull request #11)

VRFS-5004 revamp notification
This commit is contained in:
Nuwan Chaturanga 2021-04-11 18:50:51 +00:00 committed by Seth Call
commit dccd971d2e
7 changed files with 555 additions and 79 deletions

View File

@ -1690,4 +1690,4 @@ module JamRuby
description == 'TEXT_MESSAGE'
end
end
end
end

View File

@ -99,7 +99,9 @@
layoutScreens(width, height);
layoutSidebar(width, height);
layoutHeader(width, height);
layoutNotify(width, height);
layoutNotify1(width, height);
layoutNotify2(width, height);
layoutNotify3(width, height);
layoutFooter(width, height);
$(document).triggerHandler('layout_resized');
@ -325,14 +327,41 @@
$('[layout="header"]').css(css);
}
function layoutNotify(screenWidth, screenHeight) {
var $notify = $('[layout="notify"]');
var nHeight = $notify.height();
function layoutNotify1(screenWidth, screenHeight) {
var $notify1 = $('[layout="notify1"]');
//var nHeight = $notify1.height();
var notifyStyle = {
bottom: '0px',
position: 'fixed'
position: 'fixed',
padding: '20px'
};
$notify.css(notifyStyle);
$notify1.css(notifyStyle);
}
function layoutNotify2(screenWidth, screenHeight) {
var $notify1 = $('[layout="notify1"]');
var $notify2 = $('[layout="notify2"]');
var nHeight = $notify1.height();
var notifyStyle = {
bottom: (nHeight + 41).toString()+'px',
position: 'fixed',
padding: '20px'
};
$notify2.css(notifyStyle);
}
function layoutNotify3(screenWidth, screenHeight) {
var $notify1 = $('[layout="notify1"]');
var $notify2 = $('[layout="notify2"]');
var $notify3 = $('[layout="notify3"]');
var nHeight1 = $notify1.height();
var nHeight2 = $notify2.height();
var notifyStyle = {
bottom: (nHeight1 + nHeight2 + 41 + 41).toString()+'px',
position: 'fixed',
padding: '20px'
};
$notify3.css(notifyStyle);
}
function layoutFooter(screenWidth, screenHeight) {
@ -806,6 +835,16 @@
return false;
}
function expandNotificationPanel(){
if(!context.JK.currentUserId) {
showDialog('login-required-dialog');
return false;
}
expandedPanel = 'panelNotifications';
layout();
return false;
}
function wizardLinkClicked(evt) {
evt.preventDefault();
var targetStepId = $(evt.currentTarget).attr("layout-wizard-link");
@ -915,8 +954,73 @@
// used for concurrent notifications
var notifyQueue = [];
var firstNotification = false;
var notifyDetails;
var notify1Showing = false;
var notify2Showing = false;
var notify3Showing = false;
var isMouseOverNotify1 = false;
var isMouseOverNotify2 = false;
var isMouseOverNotify3 = false;
var notify1Elapsed = false;
var notify2Elapsed = false;
var notify3Elapsed = false;
var $notify1 = $('[layout="notify1"]');
var $notify2 = $('[layout="notify2"]');
var $notify3 = $('[layout="notify3"]');
$notify1.on('mouseenter', mouseEnterNotification)
$notify1.on('mouseleave', mouseLeaveToNotification)
$notify2.on('mouseenter', mouseEnterNotification)
$notify2.on('mouseleave', mouseLeaveToNotification)
$notify3.on('mouseenter', mouseEnterNotification)
$notify3.on('mouseleave', mouseLeaveToNotification)
function mouseEnterNotification(evt){
var $notify = $(evt.target);
//console.log("mouseEnter", $notify.prop("id"));
switch($notify.prop("id")){
case 'notification1':
isMouseOverNotify1 = true;
break;
case 'notification2':
isMouseOverNotify2 = true;
break;
case 'notification3':
isMouseOverNotify3 = true;
break;
}
}
function mouseLeaveToNotification(evt){
var $notify = $(evt.target);
//console.log("mouseLeave", $notify.prop("id"));
switch($notify.prop("id")){
case 'notification1':
if(notify1Elapsed){
$notify1.hide(0);
notify1Showing = false;
}
isMouseOverNotify1 = false;
break;
case 'notification2':
if(notify2Elapsed){
$notify2.hide(0);
notify2Showing = false;
}
isMouseOverNotify2 = false;
break;
case 'notification3':
if(notify3Elapsed){
$notify3.hide(0);
notify3Showing = false;
}
isMouseOverNotify3 = false;
break;
}
}
//var firstNotification = false;
//var notifyDetails;
var okButton = {
id: "btn-okay",
@ -939,7 +1043,58 @@
cancelButton
];
// this.notify = function (message, buttons, noCancel) {
// if (!buttons) {
// if (noCancel) {
// buttons = [okButton];
// }
// else {
// buttons = defaultButtons;
// }
// }
// // this allows clients to just specify the important action button without having to repeat the cancel descripton everywhere
// if (buttons.length === 1) {
// // jkolyer: changed default to remove cancel as this is used as an alert, not a confirmation (see ConfirmDialog)
// // buttons.push(cancelButton);
// }
// var $notify = $('[layout="notify"]');
// if (notifyQueue.length === 0) {
// firstNotification = true;
// setNotificationInfo(message, buttons, $notify);
// }
// notifyQueue.push({message: message, descriptor: buttons});
// // JW - speeding up the in/out parts of notify. Extending non-moving time.
// $notify.hide(0)
// .show({
// duration: 0,
// queue: true,
// complete: function () {
// notifyDetails = notifyQueue.shift();
// // shift 1 more time if this is first notification being displayed
// if (firstNotification) {
// notifyDetails = notifyQueue.shift();
// firstNotification = false;
// }
// if (notifyDetails !== undefined) {
// setNotificationInfo(notifyDetails.message, notifyDetails.descriptor, $notify);
// }
// notifyDetails = {};
// }
// })
// .delay(8000)
// .hide(0)
// };
this.notify = function (message, buttons, noCancel) {
console.log("call this.notify");
if (!buttons) {
if (noCancel) {
buttons = [okButton];
@ -949,46 +1104,182 @@
}
}
// this allows clients to just specify the important action button without having to repeat the cancel descripton everywhere
if (buttons.length === 1) {
// jkolyer: changed default to remove cancel as this is used as an alert, not a confirmation (see ConfirmDialog)
// buttons.push(cancelButton);
}
var $notify = $('[layout="notify"]');
if (notifyQueue.length === 0) {
firstNotification = true;
setNotificationInfo(message, buttons, $notify);
}
notifyQueue.push({message: message, descriptor: buttons});
// JW - speeding up the in/out parts of notify. Extending non-moving time.
$notify.slideDown(250)
.delay(4000)
.slideUp({
duration: 400,
queue: true,
complete: function () {
notifyDetails = notifyQueue.shift();
// shift 1 more time if this is first notification being displayed
if (firstNotification) {
notifyDetails = notifyQueue.shift();
firstNotification = false;
showNotificationToast();
}
function showNotificationToast(){
if(!notify1Showing){
var notifyDetails1 = notifyQueue.shift();
if (notifyDetails1 !== undefined) {
setNotificationInfo(notifyDetails1.message, notifyDetails1.descriptor, $notify1);
notify1Elapsed = false;
$notify1.hide(0)
.show({
duration: 0,
//queue: true,
complete: function () {
console.log("Show notification1")
notify1Showing = true;
notifyDetails1 = {};
}
if (notifyDetails !== undefined) {
setNotificationInfo(notifyDetails.message, notifyDetails.descriptor, $notify);
})
//.delay(8000);
setTimeout(function(){
notify1Elapsed = true;
if(!isMouseOverNotify1){
$notify1.hide(0, function(){
notify1Showing = false;
console.log("Hide notification1")
})
}
}, 8000)
}
notifyDetails = {};
}
});
};
}else if(!notify2Showing){
var notifyDetails2 = notifyQueue.shift();
if (notifyDetails2 !== undefined) {
setNotificationInfo(notifyDetails2.message, notifyDetails2.descriptor, $notify2);
notify2Elapsed = false;
$notify2.hide(0)
.show({
duration: 0,
//queue: true,
complete: function () {
console.log("Show notification2")
notify2Showing = true;
notifyDetails2 = {};
}
});
// .delay(8000)
// .hide(0, function(){
// notify2Showing = false;
// console.log("Hide notification2")
// })
setTimeout(function(){
notify2Elapsed = true;
if(!isMouseOverNotify2){
$notify2.hide(0, function(){
notify2Showing = false;
console.log("Hide notification2")
})
}
}, 8000);
}
}else if(!notify3Showing){
var notifyDetails3 = notifyQueue.shift();
if (notifyDetails3 !== undefined) {
setNotificationInfo(notifyDetails3.message, notifyDetails3.descriptor, $notify3);
notify3Elapsed = false;
$notify3.hide(0)
.show({
duration: 0,
//queue: true,
complete: function () {
console.log("Show notification3")
notify3Showing = true;
notifyDetails3 = {};
}
})
// .delay(8000)
// .hide(0, function(){
// notify3Showing = false;
// console.log("Hide notification3")
// })
setTimeout(function(){
notify3Elapsed = true;
if(!isMouseOverNotify3){
$notify3.hide(0, function(){
notify3Showing = false;
console.log("Hide notification3")
})
}
}, 8000);
}
}else if(notify1Showing && notify2Showing && notify3Showing){
//try again after 1 second
setTimeout(function(){
showNotificationToast()
}, 1000)
}
}
// function setNotificationInfo(message, buttons, notificationSelector) {
// var $notify = $('[layout="notify"]');
// $('h2', $notify).text(message.title);
// $('p', $notify).empty();
// if (message.text instanceof jQuery) {
// $('p', $notify).append(message.text)
// }
// else {
// $('p', $notify).html(message.text);
// }
// if (message.icon_url) {
// $('#avatar', $notify).attr('src', message.icon_url);
// $('#notify-avatar', $notify).show();
// }
// else {
// $('#notify-avatar', $notify).hide();
// }
// if (message.detail) {
// $('div.detail', $notify).html(message.detail).show();
// }
// else {
// $('div.detail', $notify).hide();
// }
// var $buttonDiv = $('#buttons', $notify);
// $buttonDiv.empty();
// $.each(buttons, function(index, val) {
// // build button HTML based on KV pairs
// var keys = Object.keys(val);
// var buttonHtml = '<a';
// for (var i=0; i < keys.length; i++) {
// //console.log("keys[i]=%o, val[keys[i]]=%o", keys[i], val[keys[i]]);
// // ignore button text and callback info
// if (keys[i] !== 'text' && keys[i] !== 'callback' && keys[i] !== 'callback_args') {
// buttonHtml += ' ' + keys[i] + '="' + val[keys[i]] + '"';
// }
// }
// buttonHtml += '>' + val['text'] + '</a>&nbsp;&nbsp;';
// $buttonDiv.append(buttonHtml);
// // ensure it doesn't fire twice
// $('#' + val.id, $notify).unbind('click');
// $('#' + val.id, $notify).click(function() {
// if (val.callback !== undefined) {
// if (val.callback_args) {
// val.callback(val.callback_args);
// }
// else {
// val.callback();
// }
// return false;
// }
// else {
// notificationSelector.hide();
// return false;
// }
// });
// });
// }
function setNotificationInfo(message, buttons, notificationSelector) {
var $notify = $('[layout="notify"]');
var $notify = notificationSelector;
$('h2', $notify).text(message.title);
$('p', $notify).empty();
if (message.text instanceof jQuery) {
@ -1048,7 +1339,7 @@
return false;
}
else {
notificationSelector.hide();
$notify.hide();
return false;
}
});
@ -1093,6 +1384,7 @@
this.getCurrentScreen = function() {
return currentScreen; // will be a string of the layout-id of the active screen
}
this.close = function (evt) {
close(evt);
};
@ -1156,8 +1448,12 @@
events();
};
this.expandNotificationPanel = function(){
return expandNotificationPanel()
}
return this;
};
}(window, jQuery));
}(window, jQuery));

View File

@ -95,31 +95,17 @@
function onNotificationOccurred(payload) {
queueNotificationSeen(payload.notification_id, payload.created_at, payload.description);
incrementNotificationCount();
highlightCount();
if(userCanSeeNotifications(payload)) {
app.updateNotificationSeen(payload.notification_id, payload.created_at);
}
else {
if(app.layout.isNoisyNotification(payload) && !missedNotificationsWhileAway) {
// this handles a special case--if a notification is too noisy while away, then don't bother
// incrementing anything on the sidebar or otherwise distracting the user
app.updateNotificationSeen(payload.notification_id, payload.created_at);
}
else {
queueNotificationSeen(payload.notification_id, payload.created_at, payload.description);
missedNotificationsWhileAway = true;
}
}
missedNotificationsWhileAway = true;
}
function userCameBack() {
if(isNotificationsPanelVisible()) {
if(missedNotificationsWhileAway) {
// catch user's eye, then put count to 0
pulseToDark();
// pulseToDark();
if(queuedNotificationCreatedAt) {
app.updateNotificationSeen(queuedNotification, queuedNotificationCreatedAt);
}
@ -147,9 +133,11 @@
}
function events() {
$(context).on(EVENTS.DIALOG_CLOSED, function(e, data) {if(data.dialogCount == 0) userCameBack(); });
$(window).focus(userCameBack);
$(window).blur(windowBlurred);
// $(context).on(EVENTS.DIALOG_CLOSED, function(e, data) {
// if(data.dialogCount == 0) userCameBack();
// });
//$(window).focus(userCameBack);
//$(window).blur(windowBlurred);
app.user()
.done(function(user) {
setCount(user.new_notifications);
@ -160,6 +148,8 @@
$panel.on('open', opened);
$contents.on('click', opened);
// friend notifications
registerFriendRequest();
registerFriendRequestAccepted();
@ -582,6 +572,7 @@
}
}]
);
context.JK.layout.expandNotificationPanel();
});
}
@ -635,6 +626,7 @@
}
}]
);
context.JK.layout.expandNotificationPanel();
});
}
@ -652,6 +644,7 @@
"text": payload.msg,
"icon_url": context.JK.resolveAvatarUrl(payload.photo_url)
});
context.JK.layout.expandNotificationPanel();
});
}
@ -692,7 +685,7 @@
logger.debug("Handling FRIEND_REQUEST msg " + JSON.stringify(payload));
handleNotification(payload, header.type);
app.notify({
"title": "New Friend Request",
"text": payload.msg,
@ -710,6 +703,8 @@
}
}]
);
context.JK.layout.expandNotificationPanel();
//opened()
});
}
@ -790,6 +785,7 @@
}
}]
);
context.JK.layout.expandNotificationPanel();
// THERE IS A RACE CONDITION THAT CAUSES AN ERROR WHEN INVOKING THE CODE BELOW
// THE ACTIVEMUSICSESSION HAS NOT YET BEEN FULLY CREATED B/C THE CREATOR'S CLIENT IS
@ -1342,7 +1338,7 @@
//context.ChatActions.msgReceived(payload);
// only show if chat dialog is not showing or if the focused lesson session is not the one specified in the payload
console.log("context.ChatStore.lessonSessionId", app.layout.isDialogShowing('chat-dialog'), context.ChatStore.lessonSessionId, payload.lesson_session_id)
//console.log("context.ChatStore.lessonSessionId", app.layout.isDialogShowing('chat-dialog'), context.ChatStore.lessonSessionId, payload.lesson_session_id)
if (!app.layout.isDialogShowing('chat-dialog') || context.ChatStore.lessonSessionId != payload.lesson_session_id) {
app.notify({
"title": "Lesson Message",
@ -1410,13 +1406,33 @@
$list.prepend(notificationHtml);
onNotificationOccurred(payload);
drawAttentionToNotification(payload, type);
onNotificationOccurred(payload, type);
initializeActions(payload, type);
return true;
}
function drawAttentionToNotification(payload, type){
if (type === context.JK.MessageType.FRIEND_REQUEST ||
type === context.JK.MessageType.FRIEND_REQUEST_ACCEPTED ||
type === context.JK.MessageType.JOIN_REQUEST ||
type === context.JK.MessageType.JOIN_REQUEST_APPROVED ||
type === context.JK.MessageType.SESSION_INVITATION) {
var $newLi = $list.find('li[notification-id="' + payload.notification_id + '"]');
$newLi.css("border", "solid 1px #01545f");
$newLi.css("margin-bottom", "5px");
$newLi.css("padding", "10px 0");
$newLi.css("background-color", "#1099aa");
}
}
function notificationPanelClick(){
opened()
}
function approveJoinRequest(args) {
rest.updateJoinRequest(args.join_request_id, true)
.done(function(response) {

View File

@ -1,6 +1,6 @@
@import "client/common.scss";
#notification {
.notification {
position:absolute;
left:30%;
padding:20px;
@ -14,7 +14,7 @@
box-shadow: 0px 0px 15px rgba(50, 50, 50, 1);
}
#notification h2 {
.notification h2 {
font-size: 1.5em;
color:#fff;
font-weight:200;
@ -23,7 +23,7 @@
margin-bottom:10px;
}
#notification p {
.notification p {
margin-bottom:20px;
.error-text {
@ -31,7 +31,7 @@
}
}
#notification p .text-message {
.notification p .text-message {
white-space: pre-wrap;
word-wrap: break-word;
}

View File

@ -1,4 +1,4 @@
<div layout="notify" id="notification" class="hidden">
<div layout="notify1" id="notification1" class="hidden notification">
<h2>Notification</h2>
<!-- avatar -->
<a href="#" id="notify-avatar" class="avatar_large mr20">
@ -10,3 +10,26 @@
</div>
</div>
<div layout="notify2" id="notification2" class="hidden notification">
<h2>Notification</h2>
<!-- avatar -->
<a href="#" id="notify-avatar" class="avatar_large mr20">
<img id="avatar" />
</a>
<p></p>
<div class="detail"></div>
<div id="buttons" class="right clearall">
</div>
</div>
<div layout="notify3" id="notification3" class="hidden notification">
<h2>Notification</h2>
<!-- avatar -->
<a href="#" id="notify-avatar" class="avatar_large mr20">
<img id="avatar" />
</a>
<p></p>
<div class="detail"></div>
<div id="buttons" class="right clearall">
</div>
</div>

View File

@ -11,7 +11,6 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
shared_examples_for :notification_badge do |options|
it "in correct state" do
save_screenshot("notification_highlighter.png")
#sign_in_poltergeist(user) unless page.has_selector?('h2', 'musicians')
sign_in_poltergeist(user) unless page.has_selector?('h2', text: 'musicians')
badge = find("#{NOTIFICATION_PANEL} .badge", text: options[:count], visible: :all)
@ -59,6 +58,7 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
it_behaves_like :notification_badge, highlighted: true, count:1, action: :click
end
end
@ -80,7 +80,7 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
before(:each) do
notification = Notification.send_text_message("text message", user2, user)
notification.errors.any?.should be false
find('#notification #btn-reply') # wait for notification to show, so that we know the sidebar had a chance to update
find('.notification #btn-reply') # wait for notification to show, so that we know the sidebar had a chance to update
end
it_behaves_like :notification_badge, highlighted: true, count:1
@ -91,7 +91,7 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
document_blur
notification = Notification.send_text_message("text message 2", user2, user)
notification.errors.any?.should be false
find('#notification #btn-reply')
find('.notification #btn-reply')
end
it_behaves_like :notification_badge, highlighted: true, count:1
@ -104,6 +104,19 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
end
end
end
describe "click on notification panel", focus: true do
before(:each) do
notification = Notification.send_text_message("text message", user2, user)
notification.errors.any?.should be false
find('.notification #btn-reply') # wait for notification to show, so that we know the sidebar had a chance to update
find('#sidebar-notification-list').click
end
it_behaves_like :notification_badge, highlighted: false, count: 0
end
end
end
@ -162,10 +175,111 @@ describe "Notification Highlighter", :js => true, :type => :feature, :capybara_f
badge = find("#{NOTIFICATION_PANEL} .badge", text: '1')
badge['class'].include?('highlighted').should == true
find('#notification #btn-accept', text: 'ACCEPT').click
find('.notification #btn-accept', text: 'ACCEPT').click
badge = find("#{NOTIFICATION_PANEL} .badge", text: '0')
badge['class'].include?('highlighted').should == false
end
end
describe "priority notifications", focus: true do
let(:user1) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
before(:each) do
Notification.destroy_all
Friendship.destroy_all
FriendRequest.destroy_all
JoinRequest.destroy_all
fast_signin(user2, "/client")
wait_until_curtain_gone
end
describe "friend request" do
before do
req1 = FriendRequest.new
req1.user_id = user1.id
req1.friend_id = user2.id
req1.save!
req1.reload
Notification.send_friend_request(req1.id, user1.id, user2.id)
find('#btn-notification-action', text: 'ACCEPT')
expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1)
find('.note-text', text: "#{user1.name} has sent you a friend request.")
end
it "save_screenshot" do
save_screenshot("friend_request.png")
end
it_behaves_like :notification_badge, highlighted: false, count: 0
end
describe "friend request accepted" do
before do
Notification.send_friend_request_accepted(user2.id, user1.id)
expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1)
find('.note-text', text: "#{user1.name} has accepted your friend request.")
end
it "save_screenshot" do
save_screenshot("friend_request_accepted.png")
end
it_behaves_like :notification_badge, highlighted: false, count: 0
end
describe "music session join request" do
before do
ms = FactoryGirl.create(:music_session, creator: user2)
join_request = JoinRequest.new
join_request.music_session = ms
join_request.user = user1
text = "#{user1.name} has requested to join your session."
join_request.text = text
join_request.save
Notification.send_join_request(ms, join_request, text)
expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1)
find('.note-text', text: text)
end
it "save_screenshot" do
save_screenshot("music_session_join_request.png")
end
it_behaves_like :notification_badge, highlighted: false, count: 0
end
describe "music session join request approved" do
before do
ms = FactoryGirl.create(:music_session, creator: user1)
join_request = JoinRequest.new
join_request.music_session = ms
join_request.user = user2
text = "#{user1.name} has approved your request to join the session."
join_request.text = text
join_request.save
Notification.send_join_request_approved(ms, join_request)
expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1)
find('.note-text', text: text)
end
it "save_screenshot" do
save_screenshot("music_session_join_request_approved.png")
end
it_behaves_like :notification_badge, highlighted: false, count: 0
end
describe "music session invitation" do
before do
user2.online = true
user2.save!
ms = FactoryGirl.create(:music_session, creator: user1)
text = "You have been invited to join a session by #{user1.name}."
Notification.send_session_invitation(user2, user1, ms.id)
expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1)
find('.note-text', text: text)
end
it "save_screenshot" do
save_screenshot("music_session_invitation.png")
end
it_behaves_like :notification_badge, highlighted: false, count: 0
end
end
end

View File

@ -4,11 +4,8 @@ describe "Notification Subpanel", :js => true, :type => :feature, :capybara_feat
subject { page }
let(:user) { FactoryGirl.create(:user, last_jam_locidispid: 1) }
describe "user with no notifications" do
before(:each) do
fast_signin(user, "/client")
@ -35,4 +32,34 @@ describe "Notification Subpanel", :js => true, :type => :feature, :capybara_feat
expect(page).to have_selector("#{NOTIFICATION_PANEL} li.notification-entry", count: 1)
end
end
end
describe "Notification Toast", js: true, type: :feature, capybara_feature: true do
let(:user) { FactoryGirl.create(:user) }
let(:user1) { FactoryGirl.create(:user) }
let(:user2) { FactoryGirl.create(:user) }
let(:user3) { FactoryGirl.create(:user) }
before(:each) do
fast_signin(user, "/client")
end
it "popup 3 toasts" do
wait_until_curtain_gone
create_friend_request(user1, user)
create_friend_request(user2, user)
create_friend_request(user3, user)
expect(page).to have_selector(".notification", count: 3)
end
def create_friend_request(source_user, targe_user)
req = FriendRequest.new
req.user_id = source_user.id
req.friend_id = targe_user.id
req.save!
Notification.send_friend_request(req.id, source_user.id, targe_user.id)
end
end