Skip to content

Commit eb9b082

Browse files
committed
feat(notifications): send notifications to admins when an event is autocancelled
- add new model for adminnotifications - setup socketio for real time notifications - add query to get all notifications - add mutation to change the status of notification [Delivers CON-71]
1 parent e5de19a commit eb9b082

27 files changed

+242
-118
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ jobs:
191191
./cc-test-reporter before-build
192192
. venv/bin/activate
193193
coverage combine parallel-coverage/
194-
coverage xml
194+
coverage xml -i
195195
coverage report
196196
./cc-test-reporter format-coverage -o ./.coverage -t coverage.py
197197
./cc-test-reporter upload-coverage -i .coverage

.codeclimate.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ version: "2"
22
exclude_patterns:
33
- "helpers/auth/authentication.py"
44
- "helpers/calendar/events.py"
5-
- "alembic/"
5+
- "alembic/"
6+
- "admin_notifications/__init__.py"
7+
- "admin_notifications/helpers/__init__.py"
8+
- "admin_notifications/helpers/notification_templates.py"

.coveragerc

Lines changed: 0 additions & 17 deletions
This file was deleted.
File renamed without changes.
Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1-
from flask_socketio import send, emit
21
from admin_notifications.models import AdminNotification
3-
from manage import socketio
2+
from api.location.models import Location
3+
from datetime import datetime
44
import celery
55

66

7-
@celery.task(name="create-notification")
7+
def update_notification(notification_id):
8+
notification = AdminNotification.query.filter_by(id=notification_id).first()
9+
notification.date_received = datetime.now()
10+
notification.save()
11+
12+
13+
@celery.task
814
def create_notification(title, message, location_id):
15+
"""
16+
Create notifications in the database and emit them to the client
17+
"""
18+
from manage import socketio
19+
location = Location.query.filter_by(id=location_id).first()
20+
location_name = location.name
921
notification = AdminNotification(
1022
title=title,
1123
message=message,
@@ -14,4 +26,9 @@ def create_notification(title, message, location_id):
1426
)
1527
notification.save()
1628
new_notification = {"title": title, "message": message}
17-
return socketio.emit('notification', {'notification': new_notification}, broadcast=True)
29+
return socketio.emit(
30+
f"notifications-{location_name}",
31+
{'notification': new_notification},
32+
broadcast=True,
33+
callback=update_notification(notification.id)
34+
)

admin_notifications/helpers/device_last_seen.py

Lines changed: 0 additions & 26 deletions
This file was deleted.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def event_auto_cancelled_notification(event_name, room_name):
2+
return {
3+
"title": "Event Auto cancelled.",
4+
"message": f"An event {event_name} in {room_name} \
5+
has been auto cancelled."
6+
}

admin_notifications/helpers/queue_manager.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

admin_notifications/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ class AdminNotification(Base, Utility):
1515
location_id = Column(
1616
Integer,
1717
ForeignKey('locations.id', ondelete="CASCADE"),
18-
nullable=True)
18+
nullable=True
19+
)

admin_notifications/schema.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import graphene
2+
from graphene_sqlalchemy import SQLAlchemyObjectType
3+
from graphql import GraphQLError
4+
from helpers.auth.authentication import Auth
5+
from datetime import datetime
6+
from admin_notifications.models import AdminNotification as \
7+
AdminNotificationModel
8+
9+
10+
class AdminNotifications(SQLAlchemyObjectType):
11+
"""
12+
Returns the admin_notificationspayload
13+
"""
14+
class Meta:
15+
model = AdminNotificationModel
16+
17+
18+
class NotificationsList(graphene.ObjectType):
19+
"""
20+
Class to return the types for Admin notifications
21+
\n- rooms: The rooms data
22+
"""
23+
notifications = graphene.List(AdminNotifications)
24+
25+
26+
class Query(graphene.ObjectType):
27+
all_unread_notifications = graphene.Field(
28+
NotificationsList,
29+
description="Returns a list of admin notifications"
30+
)
31+
32+
@Auth.user_roles('Admin')
33+
def resolve_all_unread_notifications(self, info):
34+
query = AdminNotifications.get_query(info)
35+
notifications = query.filter(
36+
AdminNotificationModel.status == "unread").all()
37+
return NotificationsList(notifications=notifications)
38+
39+
40+
class UpdateNotificationStatus(graphene.Mutation):
41+
"""
42+
Class to update the status of a notification
43+
"""
44+
45+
class Arguments:
46+
notification_id = graphene.Int(required=True)
47+
notification = graphene.Field(AdminNotifications)
48+
49+
@Auth.user_roles('Admin')
50+
def mutate(self, info, notification_id):
51+
query = AdminNotifications.get_query(info)
52+
unread_notifications = query.filter(
53+
AdminNotificationModel.status == "unread")
54+
notification = unread_notifications.filter(
55+
AdminNotificationModel.id == notification_id).first()
56+
if not notification:
57+
raise GraphQLError("Notification is already read or not found.")
58+
notification.status = "read"
59+
notification.date_read = datetime.now()
60+
notification.save()
61+
return UpdateNotificationStatus(notification=notification)
62+
63+
64+
class Mutation(graphene.ObjectType):
65+
update_notification_status = UpdateNotificationStatus.Field(
66+
description="Updates the status od a notification and takes the argument\
67+
\n- notification_id: The name of the room[required]")

alembic/env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from api.response.models import Response
4646
from api.tag.models import Tag
4747
from api.structure.models import Structure
48-
48+
from admin_notifications.models import AdminNotification
4949

5050
target_metadata = Base.metadata
5151

alembic/versions/1b53553ddacf_add_activity_column_to_devices_table.py

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""add admin notifications table
2+
3+
Revision ID: f0873fb8edb3
4+
Revises: 50173cb0491f
5+
Create Date: 2019-06-27 13:31:19.694650
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'f0873fb8edb3'
14+
down_revision = '50173cb0491f'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.execute('DROP TYPE statustype;')
22+
op.create_table('admin_notifications',
23+
sa.Column('id', sa.Integer(), nullable=False),
24+
sa.Column('title', sa.String(), nullable=True),
25+
sa.Column('message', sa.String(), nullable=True),
26+
sa.Column('date_received', sa.String(), nullable=True),
27+
sa.Column('date_read', sa.String(), nullable=True),
28+
sa.Column('status', sa.Enum('read', 'unread', name='statustype'), nullable=True),
29+
sa.Column('location_id', sa.Integer(), nullable=True),
30+
sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ondelete='CASCADE'),
31+
sa.PrimaryKeyConstraint('id')
32+
)
33+
# ### end Alembic commands ###
34+
35+
36+
def downgrade():
37+
# ### commands auto generated by Alembic - please adjust! ###
38+
op.drop_table('admin_notifications')
39+
# ### end Alembic commands ###

api/devices/models.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from sqlalchemy import (Column, String, Integer, DateTime, ForeignKey, Enum)
1+
from sqlalchemy import (Column, String, Integer, DateTime, ForeignKey)
22
from sqlalchemy.schema import Sequence
33
from sqlalchemy.orm import relationship
44

55
from helpers.database import Base
66
from utilities.validations import validate_empty_fields
7-
from utilities.utility import Utility, ActivityType
7+
from utilities.utility import Utility
88

99

1010
class Devices(Base, Utility):
@@ -17,7 +17,6 @@ class Devices(Base, Utility):
1717
location = Column(String, nullable=False)
1818
room_id = Column(Integer, ForeignKey('rooms.id', ondelete="CASCADE"))
1919
room = relationship('Room')
20-
activity = Column(Enum(ActivityType), default="active")
2120

2221
def __init__(self, **kwargs):
2322
validate_empty_fields(**kwargs)

api/events/schema.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
import pytz
1616
from dateutil import parser
1717
from datetime import datetime, timedelta
18+
from admin_notifications.helpers.create_notification import create_notification
19+
from admin_notifications.helpers.notification_templates import (
20+
event_auto_cancelled_notification)
1821

1922

2023
class Events(SQLAlchemyObjectType):
@@ -104,6 +107,15 @@ def mutate(self, info, **kwargs):
104107
kwargs['event_id']
105108
)
106109
event_reject_reason = 'after 10 minutes'
110+
notification_payload = event_auto_cancelled_notification(
111+
event_name=event.event_title,
112+
room_name=event.room.name
113+
)
114+
create_notification(
115+
title=notification_payload['title'],
116+
message=notification_payload['message'],
117+
location_id=event.room.location_id
118+
)
107119
if not notification.event_cancellation_notification(
108120
calendar_event,
109121
room_id,

app.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from flask_graphql import GraphQLView
44
from flask_cors import CORS
55
from flask_json import FlaskJSON
6-
from flask_socketio import SocketIO
76

87
from flask_mail import Mail
98
from config import config
@@ -12,15 +11,13 @@
1211
from healthcheck_schema import healthcheck_schema
1312
from helpers.auth.authentication import Auth
1413
from api.analytics.analytics_request import AnalyticsRequest
15-
1614
mail = Mail()
1715

1816

1917
def create_app(config_name):
2018
app = Flask(__name__)
2119
CORS(app)
2220
FlaskJSON(app)
23-
SocketIO(app)
2421
app.config.from_object(config[config_name])
2522
config[config_name].init_app(app)
2623
mail.init_app(app)

cworker.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
from celery import Celery
33
from app import create_app
4-
from admin_notifications.helpers.queue_manager import beat_schedule
4+
55

66
app = create_app(os.getenv('APP_SETTINGS') or 'default')
77
app.app_context().push()
@@ -10,13 +10,12 @@
1010
app.config.update(
1111
CELERY_BROKER_URL='redis://localhost:6379',
1212
CELERY_RESULT_BACKEND='redis://localhost:6379',
13-
CELERY_ACCEPT_CONTENT=['pickle'],
14-
CELERYBEAT_SCHEDULE=beat_schedule
13+
CELERY_ACCEPT_CONTENT=['pickle']
1514
)
1615

1716

1817
def make_celery(app):
19-
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'], include=['admin_notifications.helpers.device_last_seen', 'admin_notifications.helpers.create_notification'], # noqa 501
18+
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'], include=['admin_notifications.helpers.create_notification'], # noqa 501
2019
backend=app.config['CELERY_BROKER_URL'])
2120

2221
celery.conf.update(app.config)

fixtures/admin_notification/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)