Skip to content

CON-72 admin turn device online mutation #454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ gcloud_setup: &gcloud_setup
run:
name: setup gcloud
command: |
# install
# install
sudo curl https://dl.google.com/dl/cloudsdk/release/google-cloud-sdk.tar.gz > /tmp/google-cloud-sdk.tar.gz
sudo mkdir -p /usr/local/gcloud
sudo mkdir -p /usr/local/gcloud
sudo tar -C /usr/local/gcloud -xvf /tmp/google-cloud-sdk.tar.gz
sudo /usr/local/gcloud/google-cloud-sdk/install.sh --quiet
echo PATH=$PATH:/usr/local/gcloud/google-cloud-sdk/bin >> ~/.bashrc
Expand Down Expand Up @@ -190,8 +190,8 @@ jobs:
command: |
./cc-test-reporter before-build
. venv/bin/activate
coverage combine parallel-coverage/
coverage xml
coverage combine parallel-coverage/
coverage xml -i
coverage report
./cc-test-reporter format-coverage -o ./.coverage -t coverage.py
./cc-test-reporter upload-coverage -i .coverage
Expand Down Expand Up @@ -304,13 +304,13 @@ jobs:
command: |
if [ "$CIRCLE_BRANCH" == master ] || [ "$CIRCLE_BRANCH" == develop ]; then
touch google-service-key.json
echo $GOOGLE_CREDENTIALS_STAGING | base64 --decode >> google-service-key.json
echo $GOOGLE_CREDENTIALS_STAGING | base64 --decode >> google-service-key.json
gcloud auth activate-service-account --key-file google-service-key.json
gcloud --quiet config set project ${GOOGLE_PROJECT_ID_STAGING}
gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
else
touch google-service-key.json
echo $GOOGLE_CREDENTIALS_SANDBOX | base64 --decode >> google-service-key.json
echo $GOOGLE_CREDENTIALS_SANDBOX | base64 --decode >> google-service-key.json
gcloud auth activate-service-account --key-file google-service-key.json
gcloud --quiet config set project ${GOOGLE_PROJECT_ID_SANDBOX}
gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
Expand Down
4 changes: 3 additions & 1 deletion .codeclimate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ version: "2"
exclude_patterns:
- "helpers/auth/authentication.py"
- "helpers/calendar/events.py"
- "alembic/"
- "**/alembic/"
- "**/*__init__.py"
- "**/tests/"
2 changes: 0 additions & 2 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,3 @@ omit =

[html]
directory=html_coverage_report


2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ html_coverage_report/
.tox/
.coverage
.coverage.*
.coveragerc
setup.cfg
.cache
nosetests.xml
coverage.xml
Expand Down
Empty file added admin_notifications/__init__.py
Empty file.
Empty file.
32 changes: 32 additions & 0 deletions admin_notifications/helpers/create_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from admin_notifications.models import AdminNotification
from api.location.models import Location
from datetime import datetime


def update_notification(notification_id):
notification = AdminNotification.query.filter_by(id=notification_id).first()
notification.date_received = datetime.now()
notification.save()


def create_notification(title, message, location_id):
"""
Create notifications in the database and emit them to the client
"""
from manage import socketio
location = Location.query.filter_by(id=location_id).first()
location_name = location.name
notification = AdminNotification(
title=title,
message=message,
location_id=location_id,
status="unread"
)
notification.save()
new_notification = {"title": title, "message": message}
return socketio.emit(
f"notifications-{location_name}",
{'notification': new_notification},
broadcast=True,
callback=update_notification(notification.id)
)
33 changes: 33 additions & 0 deletions admin_notifications/helpers/device_last_seen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from datetime import datetime
from api.devices.models import Devices as DevicesModel
from utilities.utility import update_entity_fields
from admin_notifications.helpers.create_notification import create_notification
from admin_notifications.helpers.notification_templates import device_offline_notification # noqa 501
import celery


@celery.task(name='check-device-last-seen')
def notify_when_device_is_offline():
"""Asynchronous method that checks whether a device's last seen is greater\
than 24hours, turns them to offline and subsequently notify's
"""
query = DevicesModel.query
online_devices = query.filter(DevicesModel.activity == "online").all()
for device in online_devices:
device_last_seen = device.last_seen
current_time = datetime.now()
duration_offline = current_time - device_last_seen

if duration_offline.days > 1:
update_entity_fields(device, activity="offline")
device.save()

room_name = device.room.name
room_id = device.room.id
notification_payload = device_offline_notification(
room_name, room_id)
create_notification(title=notification_payload['title'],
message=notification_payload['message'],
location_id=device.room.location_id)

return online_devices
7 changes: 7 additions & 0 deletions admin_notifications/helpers/notification_templates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

def device_offline_notification(room_name, room_id):
"""Notification message when device has been offline for a while"""
return {
"title": "Device is offline",
"message": f"A device in {room_name} roomid:{room_id} is offline."
}
8 changes: 8 additions & 0 deletions admin_notifications/helpers/queue_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from datetime import timedelta
"""Celery beat schedule that checks a device's last seen every 24 hours"""
beat_schedule = {
'run-check-device-last-seen-hourly': {
'task': 'check-device-last-seen',
'schedule': timedelta(hours=24)
}
}
18 changes: 18 additions & 0 deletions admin_notifications/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from sqlalchemy import (Column, String, Enum, Integer, ForeignKey)
from helpers.database import Base
from utilities.utility import Utility, StatusType


class AdminNotification(Base, Utility):
__tablename__ = 'admin_notifications'

id = Column(Integer, primary_key=True) # noqa
title = Column(String, nullable=True)
message = Column(String, nullable=True)
date_received = Column(String, nullable=True)
date_read = Column(String, nullable=True)
status = Column(Enum(StatusType), default="unread")
location_id = Column(
Integer,
ForeignKey('locations.id', ondelete="CASCADE"),
nullable=True)
17 changes: 17 additions & 0 deletions admin_notifications/socket_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from flask_socketio import send
from admin_notifications.models import AdminNotification


def serialize_message(notification):
return {
"title": notification.title,
"message": notification.message,
}


def send_notifications():
query = AdminNotification.query
notifications = query.filter_by(status="unread").all()
notifications = [serialize_message(notification)
for notification in notifications]
return send(notifications, broadcast=True)
2 changes: 1 addition & 1 deletion alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from api.response.models import Response
from api.tag.models import Tag
from api.structure.models import Structure

from admin_notifications.models import AdminNotification

target_metadata = Base.metadata

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""add activity column to devices table

Revision ID: 79ef610dbd41
Revises: a36af2be7b0c
Create Date: 2019-06-28 08:05:37.542613

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql


# revision identifiers, used by Alembic.
revision = '79ef610dbd41'
down_revision = 'af8e4f84b552'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
activitytype = postgresql.ENUM(
'online', 'offline', name='activitytype')
activitytype.create(op.get_bind())
op.add_column('devices', sa.Column('activity', sa.Enum(
'online', 'offline', name='activitytype'), nullable=True))
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('devices', 'activity')
# ### end Alembic commands ###
5 changes: 3 additions & 2 deletions api/devices/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

from helpers.database import Base
from utilities.validations import validate_empty_fields
from utilities.utility import Utility, StateType
from utilities.utility import Utility, StateType, ActivityType


class Devices(Base, Utility):
__tablename__ = 'devices'
id = Column(Integer, Sequence('devices_id_seq', start=1, increment=1), primary_key=True) # noqa
id = Column(Integer, Sequence('devices_id_seq', start=1, increment=1), primary_key=True) # noqa
name = Column(String, nullable=False)
device_type = Column(String, nullable=False)
date_added = Column(DateTime, nullable=False)
Expand All @@ -18,6 +18,7 @@ class Devices(Base, Utility):
room_id = Column(Integer, ForeignKey('rooms.id', ondelete="CASCADE"))
room = relationship('Room')
state = Column(Enum(StateType), default="active")
activity = Column(Enum(ActivityType), default="online")

def __init__(self, **kwargs):
validate_empty_fields(**kwargs)
Expand Down
28 changes: 27 additions & 1 deletion api/devices/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def mutate(self, info, **kwargs):
room_location = location_join_room().filter(
RoomModel.id == kwargs['room_id'],
RoomModel.state == 'active'
).first()
).first()
if not room_location:
raise GraphQLError("Room not found")
user = get_user_from_db()
Expand Down Expand Up @@ -108,6 +108,28 @@ def mutate(self, info, device_id, **kwargs):
return DeleteDevice(device=exact_device)


class UpdateDeviceActivity(graphene.Mutation):
"""Changes a device activity from online to offline"""
class Arguments:
device_id = graphene.Int(required=True)
activity = graphene.String()

device = graphene.Field(Devices)

@Auth.user_roles('Admin')
def mutate(self, info, device_id, **kwargs):
query_device = Devices.get_query(info)
result = query_device.filter(DevicesModel.activity == "offline")
exact_device = result.filter(
DevicesModel.id == device_id
).first()
if not exact_device:
Copy link
Contributor

Choose a reason for hiding this comment

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

@Arusey, you could make the message a bit more specific. When the device is online and the user tries to turn the device online, the message Device not found is a bit confusing. You can make the message to be Device not found or already turned online.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

noted

raise GraphQLError("Device not found")
update_entity_fields(exact_device, activity="online", **kwargs)
exact_device.save()
return UpdateDeviceActivity(device=exact_device)


class Query(graphene.ObjectType):
"""
Query to get list of all devices
Expand Down Expand Up @@ -193,6 +215,10 @@ class Mutation(graphene.ObjectType):
[required]")
delete_device = DeleteDevice.Field(
description="Deletes a given device given the arguments to delete\
\n- device_id: Unique identifier of the tag\
[required]")
update_device_activity = UpdateDeviceActivity.Field(
description="admin updates the device activity from offline to online\
\n- device_id: Unique identifier of the tag\
[required]"
)
1 change: 1 addition & 0 deletions celerybeat.pid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
35
19 changes: 16 additions & 3 deletions cworker.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import os

from celery import Celery
from app import create_app

from admin_notifications.helpers.queue_manager import beat_schedule

app = create_app(os.getenv('APP_SETTINGS') or 'default')
app.app_context().push()


app.config.update(
CELERY_BROKER_URL=os.getenv('CELERY_BROKER_URL'),
CELERY_RESULT_BACKEND=os.getenv('CELERY_RESULT_BACKEND'),
CELERY_ACCEPT_CONTENT=['pickle'],
CELERYBEAT_SCHEDULE=beat_schedule
)


def make_celery(app):
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'], include=['admin_notifications.helpers.device_last_seen', 'admin_notifications.helpers.create_notification'], # noqa 501
backend=app.config['CELERY_BROKER_URL'])

celery.conf.update(app.config)
celery.conf.enable_utc = False
TaskBase = celery.Task

class ContextTask(TaskBase):
Expand All @@ -24,3 +34,6 @@ def __call__(self, *args, **kwargs):


celery = make_celery(app)

celery_scheduler = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery_scheduler.conf.enable_utc = False
3 changes: 2 additions & 1 deletion docker/dev/start_redis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
#done
cd /app
export $(cat .env | xargs)
celery worker -A cworker.celery --loglevel=info
celery worker -A cworker.celery --loglevel=info &
celery -A cworker.celery beat -l info
3 changes: 2 additions & 1 deletion docker/prod/start_redis.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/bash
cd /app
export $(cat .env | xargs)
celery worker -A cworker.celery --loglevel=info
celery worker -A cworker.celery --loglevel=info &
celery -A cworker.celery beat -l info
17 changes: 17 additions & 0 deletions fixtures/analytics/query_all_analytics_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@
"durationInMinutes": 0
}
]
},
{
"roomName": "Buluma",
"cancellations": 0,
"cancellationsPercentage": 0.0,
"autoCancellations": 0,
"numberOfBookings": 0,
"checkins": 0,
"checkinsPercentage": 0.0,
"bookingsPercentageShare": 0.0,
"appBookings": 0,
"appBookingsPercentage": 0.0,
"events": [
{
"durationInMinutes": 0
}
]
}
],
"bookingsCount": [
Expand Down
Loading