Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
dc5e6f8
Add initial layout for notifications framework
MridulS Jul 22, 2016
3d9a85d
Add spec for share graph event db functions
MridulS Jul 22, 2016
c79c1bd
Add boilerplate test
MridulS Jul 22, 2016
5cd7500
Add error page if user is not logged in for notifications
MridulS Jul 24, 2016
d507c44
Update ShareGraphEvent model and implement functions in db.py
MridulS Jul 28, 2016
1dd3d6b
update login error text
MridulS Jul 28, 2016
47feb76
create new event after sharing graph with group
MridulS Jul 28, 2016
0e9465f
add support for reading notifications
MridulS Jul 31, 2016
6982837
add boilerplate exceptions file
MridulS Aug 1, 2016
ec85ee2
Add functionality to read all events of a group
MridulS Aug 1, 2016
f630f8a
update notifications template
MridulS Aug 5, 2016
4c24e6c
add functionality to view all notifications of a group
MridulS Aug 5, 2016
db07aa5
changes
MridulS Aug 11, 2016
e98ff6f
Merge branch 'master' into notification
MridulS Aug 11, 2016
99c3aff
Update notifications code according to the spec
MridulS Aug 14, 2016
80124a4
update UI for notifications
MridulS Aug 14, 2016
7d5a2c9
removing unnecessary packages
adbharadwaj Aug 16, 2016
24ea0e2
refactoring get all notifications flow-still incomplete
adbharadwaj Aug 16, 2016
3706261
refactored notifications flow
adbharadwaj Aug 16, 2016
9c672dd
Update notifications.js according to notifications spec
MridulS Aug 17, 2016
3542ff8
Add group information and the number badges to the left column in not…
MridulS Aug 17, 2016
8cd3d09
update urls.py to point to correct view
MridulS Aug 17, 2016
d9f31bb
Update views and db to match the notifications spec
MridulS Aug 17, 2016
d4f786a
Remove unnecessary file get-pip
MridulS Aug 17, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions graphs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
ex. 'id' for user table would be 'user_id'
'''

from sqlalchemy import Column, Integer, String, ForeignKey, Table, Index, ForeignKeyConstraint
from sqlalchemy import Column, Integer, String, ForeignKey, Table, Index, ForeignKeyConstraint, Boolean, UniqueConstraint
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref
from sqlalchemy.types import TIMESTAMP
from sqlalchemy.types import TIMESTAMP, Boolean
from django.db import models
from django.conf import settings
from sqlalchemy import create_engine
Expand Down Expand Up @@ -316,6 +316,33 @@ class Edge(Base):
ForeignKeyConstraint([user_id, graph_id, tail_node_id], [Node.user_id, Node.graph_id, Node.node_id], ondelete="CASCADE", onupdate="CASCADE"), {})
#no relationship specified

class ShareGraphEvent(Base):
__tablename__ = 'share_graph_event'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need some docs on the schema. What does the table store? Why do you need each attribute?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Class names should have camel case.

# unique id for each share graph event
id = Column(Integer, autoincrement=True, primary_key=True)
# id of the graph shared
graph_id = Column(String, nullable=False)
# id of the owner of the graph which is shared
owner_id = Column(String, nullable=False)
# id of the group the graph is shared in
group_id = Column(String, ForeignKey('group.group_id', ondelete="CASCADE", onupdate="CASCADE"), nullable=False)
# id of the member of the group.
# Hence there can be multiple share graph events if a owner shares a grap
# with a group. A share graph event will be created for all the memebers
# of the group except the owner of the graph (the one who shared it).
member_id = Column(String, ForeignKey('user.user_id', ondelete="CASCADE", onupdate="CASCADE"), nullable=False)
# timestamp at which the share graph event occured
share_time = Column(TIMESTAMP, nullable = False)
# Boolean value to track if notifications is read or not.
# if True then the notification is active, i.e not read
is_active = Column(Boolean, nullable=False)
# We use ForeignKeyConstraint for graph_id and owner_id
# because this is the only to define a composite foreign key
__table_args__ = (
UniqueConstraint('graph_id', 'owner_id', 'group_id', 'member_id'),
ForeignKeyConstraint([graph_id, owner_id], [Graph.graph_id, Graph.user_id], ondelete="CASCADE", onupdate="CASCADE"),
)

#Create indices
Index('graph_public_idx', Graph.public)
Index('graph_owner_idx', Graph.user_id)
Expand Down
3 changes: 3 additions & 0 deletions graphs/static/graphs/css/notifications.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.notification_read {
background-color: #bdbdbd;
}
97 changes: 97 additions & 0 deletions graphs/static/graphs/js/notifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
$(document).ready(function () {
/**
* Mark notification as read through the UI.
*/


// Method to mark as read individual notifications
$(".read_notification").click(function(e){
// get the user id of the logged in user
var uid = $(this).attr('uid');
// get a list of notification ids, in this case it will be a
// list of length 1 as it a single notification
var notification_ids = [$(this).attr('notification_id')];
// Call function read_notification which sends a POST request to mark
// notification as read
read_notifications(uid, notification_ids, function(error, data){
if (error) {
return alert(error);
} else {
// If no error is caught that manipulate the DOM for notification
manipulate_dom(notification_ids);
}
});
});

// Method to mark all notification of a group as read for the user
$(".read_all_group_notifications").click(function(e){
// get the user id of the logged in user
var uid = $(this).attr('uid');
// get the group id of the notifications that need to be marked as read
var group_id = $(this).attr('group_id');
// create a list of notification ids which are present in the group
var notification_ids = [];
$.each($('.notifications.' + group_id), function( index, notification ) {
notification_ids.push($(notification).attr('id'));
});
// Call function read_notifications which sends a POST request to mark
// notifications as read
read_notifications(uid, notification_ids, function(error, data){
if (error) {
return alert(error);
} else {
// If no error is caught that manipulate the DOM for notification
manipulate_dom(notification_ids);
}
});
});

// Method to mark all notifications as read for the user
$(".read_all_notifications").click(function(e){
// get the user id of the logged in user
var uid = $(this).attr('uid');
// create a list of notification ids which are present in the group
var notification_ids = [];
$.each($('.notifications'), function( index, notification ) {
notification_ids.push($(notification).attr('id'));
});
// Call function read_notification which sends a POST request to mark
// notifications as read
read_notifications(uid, notification_ids, function(error, data){
if (error) {
return alert(error);
} else {
// If no error is caught that manipulate the DOM for notification
manipulate_dom(notification_ids);
$
}
});
});

// send a POST request to the view method mark_notificartions_as_read_api
// with a list of notification_ids
var read_notifications = function(uid, notification_ids, callback) {
console.log(notification_ids);
$.post('/javascript/' + uid + '/mark_notifications_as_read_api/', {
'notification_ids[]': [notification_ids]
}, function (data) {
if (data.Error) {
callback(data.Error, null);
} else {
callback(null, data);
}
});
};

// grey out notification row and remove the tick mark for notification row
var manipulate_dom = function(notification_ids){
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename method name to "grey_out_notifications"

$.each($(notification_ids), function(index, element){
$('.remove_read'+element).remove();
$('.notification_event'+element).addClass('notification_read');
});
};

// helper function for tooltip, used to display text when cursors hovers on
// the read tick marks.
$('[data-toggle="tooltip"]').tooltip();
});
5 changes: 4 additions & 1 deletion graphs/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ <h4 class="modal-title" id="myModalLabel">Forgot Information</h4>
{% endif %}
<li><a href="{% url 'features' %}">Features</a></li>
<li><a href="{% url 'help_tutorial' %}">Help</a></li>
<li><a href="{% url 'help_about' %}" style="text-align:center">About Us</a></li>
<li><a href="{% url 'help_about' %}" style="text-align:center">About Us</a></li>
{% if uid != None %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this check sufficient? Should we not check that the user is indeed logged in and is a "valid" user? I am asking without knowledge of how this check is performed in other parts of GraphSpace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this will be enough as uid gets a value only if the user is logged in and a valid user. Logic of this https://github.com/Murali-group/GraphSpace/blob/master/graphs/auth/login.py#L44

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

screen shot 2016-07-24 at 4 52 03 pm
The user will get a screen like this if its not a valid log in

<li><a href="{% url 'notifications' uid %}">Notifications</a></li>
{% endif %}
</ul>

<!-- display login form if not logged in -->
Expand Down
72 changes: 72 additions & 0 deletions graphs/templates/graphs/notifications.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{% extends 'base.html' %}
{% block content %}
{% load staticfiles %}
<link href={% static "graphs/css/notifications.css" %} rel="stylesheet">
<script type="text/javascript" src="{% static 'graphs/js/notifications.js' %}"></script>

{% if uid != None %}

<br>
<div class="row"><button style="position: relative;
right: 100px; float: right;" class="btn btn-default read_all_notifications" uid="{{ uid }}" allid="True">Mark all notifications as read</button></div>
<br>

<div class="row">
<div class="col-sm-3">
<div class="list-group">
<a href="{% url 'notifications' uid %}" class="list-group-item">Unread<span class="badge">{{ num_active_notifications }}</span> </a>
<a href="{% url 'notifications' uid %}?all=1" class="list-group-item">All Notifications <span class="badge">{{ num_notifications }}</span> </a>
</div>
<div class="list-group">
<a href="#" class="list-group-item active">Groups</a>
{% for group_id, notifications in groups_of_user.items %}
<a href="{% url 'notifications' uid %}?group_id={{ group_id }}" class="list-group-item">{{ group_id }} <span class="badge">{{ notifications }}</span></a>
{% endfor %}
</div>
</div>

{% if grouped_notifications|length != 0 %}
<div class="col-sm-8">
<div class="panel panel-primary">
{% for group, events in grouped_notifications.items %}
{% if events|length > 0 %}

<table class="table" id="notification_table{{ group }}">
<tr bgcolor="#428BCA">
<th><font color="white">{{ group }}</font></th>
<th></th>
<th><a href="#" data-toggle="tooltip" title="Mark all {{ group }} notifications as read" data-placement="left"><button class="btn btn-default read_all_group_notifications" uid="{{ uid }}" group_id="{{ group }}"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button></a> </th>
</tr>
{% for event in events %}
{% if event.owner_id != uid %}

<tr id="{{ event.id }}" class="notification_event{{ event.id }} {{ group }} notifications">
<td> <a href="{% url 'graphs' %}/{{ event.owner_id }}/{{ event.graph_id}}">{{ event.owner_id }} shared {{ event.graph_id }} with {{ event.group_id }}.</a></td>
<td> Shared on {{ event.share_time }}</td>
<td class="remove_read{{ event.id }}"><a href="#" data-toggle="tooltip" title="Mark as read" data-placement="left"><button class="btn btn-default read_notification" notification_id="{{ event.id }}" uid="{{ event.member_id }}"><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></button></a></td>
</tr>

{% endif %}
{% endfor %}
</table>
{% endif %}
{% endfor %}
</div>
</div>


{% else %}
<div class="col-sm-8">
<div class="panel panel-primary">
<div class="panel-heading">
<h4 class="panel-title" style="text-align: center">No new notifications.</h4>
</div>
</div>
</div>
{% endif %}


</div>

{% endif %}
{% endblock %}
6 changes: 6 additions & 0 deletions graphs/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
url(r'^graphs/public/$', views.public_graphs, name='public_graphs'),
url(r'^graphs/upload/$', views.upload_graph_through_ui, name='upload_graph_through_ui'),

# notifications page
url(r'^(?P<uid>\b[A-Z0-9a-z._%+-]+@[A-Z0-9a-z.-]+\.[A-Za-z]{2,4}\b)/notifications/$', views.notifications, name='notifications'),

# view graph page. This contains regular expression to catch url in the form of the following:
# /graphs/email_address/graph_id/
# regex from http://www.regular-expressions.info/email.html
Expand Down Expand Up @@ -77,6 +80,8 @@
url(r'^resetPassword/$', views.resetPassword, name='resetPassword'),
url(r'^launchTask/$', views.launchTask, name='launchTask'),
url(r'^retrieveTaskCode/$', views.retrieveTaskCode, name='retrieveTaskCode'),
url(r'^javascript/(?P<uid>\b[A-Z0-9a-z._%+-]+@[A-Z0-9a-z.-]+\.[A-Za-z]{2,4}\b)/mark_notifications_as_read_api/$', views.mark_notifications_as_read_api, name='mark_notifications_as_read_api'),


#REST API

Expand All @@ -90,6 +95,7 @@
url(r'^api/users/(?P<user_id>.+)/graph/makeGraphPrivate/(?P<graphname>.+)/$', views.make_graph_private, name='make_graph_private'),
url(r'^api/users/(?P<user_id>.+)/graphs/$', views.view_all_graphs_for_user, name='view_all_graphs_for_user'),


# Group REST API endpoints
url(r'^api/groups/get/(?P<group_owner>.+)/(?P<groupname>.+)/$', views.get_group, name='get_group'),
url(r'^api/groups/get/$', views.get_groups, name='get_groups'),
Expand Down
Loading