diff --git a/api/analytics/all_analytics_query.py b/api/analytics/all_analytics_query.py index 9b9e3ccf0..c1becf290 100644 --- a/api/analytics/all_analytics_query.py +++ b/api/analytics/all_analytics_query.py @@ -1,12 +1,13 @@ import graphene from helpers.auth.admin_roles import admin_roles from helpers.calendar.all_analytics_helper import ( - AllAnalyticsHelper, Event, BookingsCount + AllAnalyticsHelper, Event, BookingsCount, DeviceAnalytics ) from api.role.schema import Role from helpers.calendar.analytics_helper import CommonAnalytics from helpers.auth.authentication import Auth from api.room.schema import Room +from api.devices.schema import Devices from utilities.utility import percentage_formater from helpers.auth.user_details import get_user_from_db from utilities.validator import verify_location_id @@ -37,6 +38,7 @@ class AllAnalytics(graphene.ObjectType): cancellations_percentage = graphene.Float() app_bookings_percentage = graphene.Float() bookings_count = graphene.List(BookingsCount) + device_analytics = graphene.List(DeviceAnalytics) class Query(graphene.ObjectType): @@ -69,6 +71,11 @@ def resolve_all_analytics(self, info, **kwargs): start_date, end_date = CommonAnalytics.all_analytics_date_validation( self, start_date, end_date ) + device_query = Devices.get_query(info) + device_analytics = AllAnalyticsHelper.get_devices_analytics( + self, + device_query + ) query = Room.get_query(info) room_analytics, bookings, percentages_dict, bookings_count = \ AllAnalyticsHelper.get_all_analytics( @@ -98,6 +105,14 @@ def resolve_all_analytics(self, info, **kwargs): events=analytic['room_events'], ) analytics.append(current_analytic) + device_analytics_list = [] + for device_object in device_analytics: + device_analytic = DeviceAnalytics( + device_name=device_object['device_name'], + device_id=device_object['device_id'], + down_time=device_object['down_time'] + ) + device_analytics_list.append(device_analytic) return AllAnalytics( bookings=bookings, checkins_percentage=percentage_formater( @@ -117,4 +132,5 @@ def resolve_all_analytics(self, info, **kwargs): bookings ), bookings_count=bookings_count, - analytics=analytics) + analytics=analytics, + device_analytics=device_analytics_list) diff --git a/fixtures/analytics/query_all_analytics_fixtures.py b/fixtures/analytics/query_all_analytics_fixtures.py index f7b05fc61..33275c04c 100644 --- a/fixtures/analytics/query_all_analytics_fixtures.py +++ b/fixtures/analytics/query_all_analytics_fixtures.py @@ -1,4 +1,4 @@ -all_analytics_query = ''' +all_analytics_query = """ query { allAnalytics(startDate:"jul 11 2018", endDate:"jul 12 2018", locationId:1) { # noqa: E501 checkinsPercentage @@ -25,10 +25,23 @@ totalBookings period } + deviceAnalytics{ + deviceName + deviceId + } } } -''' -all_analytics_query_invalid_locationid = ''' +""" +all_analytics_query_down_time = """ + query { + allAnalytics(startDate:"jul 11 2018", endDate:"jul 12 2018", locationId:1) { # noqa: E501 + deviceAnalytics{ + downTime + } + } + } +""" +all_analytics_query_invalid_locationid = """ query { allAnalytics(startDate:"jul 11 2018", endDate:"jul 12 2018", locationId:10) { # noqa: E501 checkinsPercentage @@ -57,84 +70,69 @@ } } } -''' +""" all_analytics_query_response = { - "data": { - "allAnalytics": { - "checkinsPercentage": 0.0, - "appBookingsPercentage": 0.0, - "autoCancellationsPercentage": 0.0, - "cancellationsPercentage": 0.0, - "bookings": 1, - "analytics": [ - { - "roomName": "Entebbe", - "cancellations": 0, - "cancellationsPercentage": 0.0, - "autoCancellations": 0, - "numberOfBookings": 1, - "checkins": 0, - "checkinsPercentage": 0.0, - "bookingsPercentageShare": 100.0, - "appBookings": 0, - "appBookingsPercentage": 0.0, - "events": [ - { - "durationInMinutes": 45 - } - ] - }, - { - "roomName": "Tana", - "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": [ - { - "totalBookings": 1, - "period": "Jul 11 2018" - }, - { - 'totalBookings': 0, - 'period': 'Jul 12 2018' + "data": { + "allAnalytics": { + "checkinsPercentage": 0.0, + "appBookingsPercentage": 0.0, + "autoCancellationsPercentage": 0.0, + "cancellationsPercentage": 0.0, + "bookings": 1, + "analytics": [ + { + "roomName": "Entebbe", + "cancellations": 0, + "cancellationsPercentage": 0.0, + "autoCancellations": 0, + "numberOfBookings": 1, + "checkins": 0, + "checkinsPercentage": 0.0, + "bookingsPercentageShare": 100.0, + "appBookings": 0, + "appBookingsPercentage": 0.0, + "events": [{"durationInMinutes": 45}], + }, + { + "roomName": "Tana", + "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": [ + {"totalBookings": 1, "period": "Jul 11 2018"}, + {"totalBookings": 0, "period": "Jul 12 2018"}, + ], + "deviceAnalytics": [ + { + "deviceName": "Samsung", + "deviceId": 1, + # "downTime": "this device was seen {}".format( + # downtime_value), + } + ], } - ] } - } } all_analytics_query_response_super_admin_with_invalid_locationid = { "errors": [ { "message": "Location Id does not exist", - "locations": [ - { - "line": 3, - "column": 7 - } - ], - "path": [ - "allAnalytics" - ] + "locations": [{"line": 3, "column": 7}], + "path": ["allAnalytics"], } ], - "data": { - "allAnalytics": None - } + "data": {"allAnalytics": None}, } all_analytics_query_response_super_admin = { @@ -157,11 +155,7 @@ "bookingsPercentageShare": 50.0, "appBookings": 0, "appBookingsPercentage": 0.0, - "events": [ - { - "durationInMinutes": 890 - } - ] + "events": [{"durationInMinutes": 890}], }, { "roomName": "Krypton", @@ -174,11 +168,7 @@ "bookingsPercentageShare": 25.0, "appBookings": 0, "appBookingsPercentage": 0.0, - "events": [ - { - "durationInMinutes": 155 - } - ] + "events": [{"durationInMinutes": 155}], }, { "roomName": "Bujumbura", @@ -191,11 +181,7 @@ "bookingsPercentageShare": 25.0, "appBookings": 0, "appBookingsPercentage": 0.0, - "events": [ - { - "durationInMinutes": 92 - } - ] + "events": [{"durationInMinutes": 92}], }, { "roomName": "Kampala", @@ -208,11 +194,7 @@ "bookingsPercentageShare": 0.0, "appBookings": 0, "appBookingsPercentage": 0.0, - "events": [ - { - "durationInMinutes": 0 - } - ] + "events": [{"durationInMinutes": 0}], }, { "roomName": "Algiers", @@ -225,28 +207,18 @@ "bookingsPercentageShare": 0.0, "appBookings": 0, "appBookingsPercentage": 0.0, - "events": [ - { - "durationInMinutes": 0 - } - ] - } + "events": [{"durationInMinutes": 0}], + }, ], "bookingsCount": [ - { - "totalBookings": 7, - "period": "Jul 11 2018" - }, - { - "totalBookings": 5, - "period": "Jul 12 2018" - } - ] + {"totalBookings": 7, "period": "Jul 11 2018"}, + {"totalBookings": 5, "period": "Jul 12 2018"}, + ], } } } -analytics_query_for_date_ranges = ''' +analytics_query_for_date_ranges = """ query { allAnalytics(startDate:"jul 11 2020", endDate:"jul 12 2018") { checkinsPercentage @@ -275,4 +247,4 @@ } } } -''' +""" diff --git a/fixtures/devices/devices_fixtures.py b/fixtures/devices/devices_fixtures.py index 51bcc086a..e92747542 100644 --- a/fixtures/devices/devices_fixtures.py +++ b/fixtures/devices/devices_fixtures.py @@ -17,7 +17,7 @@ "allDevices": [ { "id": "1", - "lastSeen": "2018-06-08T11:17:58.785136", # noqa: E501 + "lastSeen": "2019-04-10T13:28:45", # noqa: E501 "dateAdded": "2018-06-08T11:17:58.785136", # noqa: E501 "name": "Samsung", "location": "Kampala" @@ -44,7 +44,7 @@ { "dateAdded": "2018-06-08T11:17:58.785136", "id": "1", - "lastSeen": "2018-06-08T11:17:58.785136", + "lastSeen": "2019-04-10T13:28:45", "location": "Kampala", "name": "Samsung" } @@ -68,7 +68,7 @@ "data": { "specificDevice": { "id": "1", - "lastSeen": "2018-06-08T11:17:58.785136", # noqa: E501 + "lastSeen": "2019-04-10T13:28:45", # noqa: E501 "dateAdded": "2018-06-08T11:17:58.785136", # noqa: E501 "name": "Samsung", "location": "Kampala" diff --git a/helpers/calendar/all_analytics_helper.py b/helpers/calendar/all_analytics_helper.py index 31ddb0928..40fc8e4ab 100644 --- a/helpers/calendar/all_analytics_helper.py +++ b/helpers/calendar/all_analytics_helper.py @@ -2,9 +2,10 @@ import pytz import dateutil.parser from graphql import GraphQLError +from datetime import datetime +from dateutil.relativedelta import relativedelta from helpers.calendar.analytics_helper import CommonAnalytics from utilities.utility import percentage_formater -from dateutil.relativedelta import relativedelta class Event(graphene.ObjectType): @@ -16,8 +17,13 @@ class BookingsCount(graphene.ObjectType): total_bookings = graphene.Int() -class AllAnalyticsHelper: +class DeviceAnalytics(graphene.ObjectType): + device_name = graphene.String() + device_id = graphene.Int() + down_time = graphene.String() + +class AllAnalyticsHelper: def count_bookings_within_period(events, date_pattern, string_date): """ Counts the bookings within a specified period @@ -25,7 +31,9 @@ def count_bookings_within_period(events, date_pattern, string_date): user_time_zone = CommonAnalytics.get_user_time_zone() bookings = 0 for event in events: - start_timez = dateutil.parser.parse(event.start_time).astimezone(pytz.timezone(user_time_zone)) # noqa + start_timez = dateutil.parser.parse(event.start_time).astimezone( + pytz.timezone(user_time_zone) + ) # noqa if start_timez.strftime(date_pattern) == string_date: bookings += 1 return bookings @@ -37,7 +45,9 @@ def map_bookings_to_period(dates, date_pattern, events): bookings_count = [] for date in dates: string_date = dateutil.parser.parse(date[0]).strftime(date_pattern) - bookings = AllAnalyticsHelper.count_bookings_within_period(events, date_pattern, string_date) # noqa + bookings = AllAnalyticsHelper.count_bookings_within_period( + events, date_pattern, string_date + ) output = BookingsCount(period=string_date, total_bookings=bookings) bookings_count.append(output) return bookings_count @@ -46,8 +56,8 @@ def bookings_count(self, unconverted_dates, events): """ Get bookings count and period in a given room """ - start_date = unconverted_dates['start'] - day_after_end_date = unconverted_dates['end'] + start_date = unconverted_dates["start"] + day_after_end_date = unconverted_dates["end"] parsed_start_date = dateutil.parser.parse(start_date) parsed_end_date = dateutil.parser.parse(day_after_end_date) parsed_day_after_end_date = parsed_end_date + relativedelta(days=1) @@ -56,13 +66,14 @@ def bookings_count(self, unconverted_dates, events): date_pattern = "%b %d %Y" if number_of_days <= 30: dates = CommonAnalytics.get_list_of_dates( - start_date, number_of_days) + start_date, number_of_days) else: dates = CommonAnalytics.get_list_of_month_dates( start_date, parsed_start_date, day_after_end_date, - parsed_day_after_end_date) + parsed_day_after_end_date, + ) date_pattern = "%b %Y" return AllAnalyticsHelper.map_bookings_to_period( @@ -73,30 +84,88 @@ def get_events_statistics(self, events): Gets total checkins, app_bookings... from events """ events_stats = { - "checkins": 0, - "app_bookings": 0, - "auto_cancellations": 0, - "cancellations": 0, - "duration_in_minutes": 0 + "checkins": 0, + "app_bookings": 0, + "auto_cancellations": 0, + "cancellations": 0, + "duration_in_minutes": 0, } for event in events: - events_stats['checkins'] += bool(event.checked_in) - events_stats['app_bookings'] += bool(event.app_booking) - events_stats['auto_cancellations'] += bool(event.auto_cancelled) - events_stats['cancellations'] += bool(event.cancelled) - events_stats['duration_in_minutes'] += CommonAnalytics.get_time_duration_for_event( # noqa + events_stats["checkins"] += bool(event.checked_in) + events_stats["app_bookings"] += bool(event.app_booking) + events_stats["auto_cancellations"] += bool(event.auto_cancelled) + events_stats["cancellations"] += bool(event.cancelled) + events_stats[ + "duration_in_minutes" + ] += CommonAnalytics.get_time_duration_for_event( # noqa self, event.start_time, event.end_time ) return events_stats + def convert_to_human_readable(date_time): + """ + converts a datetime object to the + format "X days, Y hours ago" + + @param date_time: datetime object + + @return: + Human readable date time + """ + user_time_zone = CommonAnalytics.get_user_time_zone() + local_time = datetime.now().astimezone( + pytz.timezone(user_time_zone) + ) + date_now = local_time.strftime("%Y-%m-%d %H:%M:%S") + current_time = datetime.strptime(str(date_now), "%Y-%m-%d %H:%M:%S") + last_seen = datetime.strptime(str(date_time), "%Y-%m-%d %H:%M:%S") + difference = relativedelta(current_time, last_seen) + years = difference.years + months = difference.months + days = difference.days + hours = difference.hours + minutes = difference.minutes + result = { + "years": years, + "months": months, + "days": days, + "hours": hours, + "minutes": minutes, + } + date = [] + for key, value in result.items(): + if value > 0: + date_string = str(value) + " " + key + date.append(date_string) + + return ", ".join(date) + " ago." + + def get_devices_analytics(self, query): + """ + Get devices str(diff.months) + analytic + """ + devices = query.filter_by(state="active").all() + device_analytics = [] + for device in devices: + down_time = AllAnalyticsHelper.convert_to_human_readable( + device.last_seen) + device_obj = { + "device_name": device.name, + "device_id": device.id, + "down_time": "this device was seen {}".format(down_time), + } + device_analytics.append(device_obj) + + return device_analytics + def get_all_analytics(self, query, **kwargs): """ Get all room analytics """ - start_date = kwargs['start_date'] - end_date = kwargs['end_date'] - location_id = kwargs['location_id'] - unconverted_dates = kwargs['unconverted_dates'] + start_date = kwargs["start_date"] + end_date = kwargs["end_date"] + location_id = kwargs["location_id"] + unconverted_dates = kwargs["unconverted_dates"] rooms = query.filter_by(state="active", location_id=location_id).all() room_analytics = [] bookings = 0 @@ -110,44 +179,54 @@ def get_all_analytics(self, query, **kwargs): events = [] try: events_result = CommonAnalytics.get_all_events_in_a_room( - self, room.id, start_date, end_date) + self, room.id, start_date, end_date + ) except GraphQLError: continue all_events += events_result bookings += len(events_result) - events_stats = AllAnalyticsHelper.get_events_statistics(self, events_result) # noqa + events_stats = AllAnalyticsHelper.get_events_statistics( + self, events_result + ) # noqa current_event = Event( - duration_in_minutes=events_stats['duration_in_minutes']) + duration_in_minutes=events_stats["duration_in_minutes"] + ) events.append(current_event) - total_checkins += events_stats['checkins'] - total_app_bookings += events_stats['app_bookings'] - total_auto_cancellations += events_stats['auto_cancellations'] - total_cancellations += events_stats['cancellations'] + total_checkins += events_stats["checkins"] + total_app_bookings += events_stats["app_bookings"] + total_auto_cancellations += events_stats["auto_cancellations"] + total_cancellations += events_stats["cancellations"] num_of_events = len(events_result) room_analytic = { - 'number_of_meetings': num_of_events, - 'room_name': room.name, - 'num_of_events': num_of_events, - 'room_events': events, - 'cancellations': events_stats['cancellations'], - 'checkins': events_stats['checkins'], - 'auto_cancellations': events_stats['auto_cancellations'], - 'cancellations_percentage': percentage_formater( - events_stats['cancellations'], num_of_events + "number_of_meetings": num_of_events, + "room_name": room.name, + "num_of_events": num_of_events, + "room_events": events, + "cancellations": events_stats["cancellations"], + "checkins": events_stats["checkins"], + "auto_cancellations": events_stats["auto_cancellations"], + "cancellations_percentage": percentage_formater( + events_stats["cancellations"], num_of_events ), - 'checkins_percentage': percentage_formater( - events_stats['checkins'], num_of_events + "checkins_percentage": percentage_formater( + events_stats["checkins"], num_of_events ), - 'app_bookings': events_stats['app_bookings'], - 'app_bookings_percentage': percentage_formater(events_stats['app_bookings'], num_of_events) # noqa + "app_bookings": events_stats["app_bookings"], + "app_bookings_percentage": percentage_formater( + events_stats["app_bookings"], num_of_events + ), # noqa } room_analytics.append(room_analytic) - room_analytics.sort(key=lambda x: x['number_of_meetings'], reverse=True) # noqa + room_analytics.sort( + key=lambda x: x["number_of_meetings"], reverse=True + ) # noqa percentages_dict = { - 'total_checkins': total_checkins, - 'total_auto_cancellations': total_auto_cancellations, - 'total_app_bookings': total_app_bookings, - 'total_cancellations': total_cancellations + "total_checkins": total_checkins, + "total_auto_cancellations": total_auto_cancellations, + "total_app_bookings": total_app_bookings, + "total_cancellations": total_cancellations, } - bookings_count += AllAnalyticsHelper.bookings_count(self, unconverted_dates, all_events) # noqa + bookings_count += AllAnalyticsHelper.bookings_count( + self, unconverted_dates, all_events + ) # noqa return room_analytics, bookings, percentages_dict, bookings_count diff --git a/tests/base.py b/tests/base.py index d44febaff..a6bcad9c8 100644 --- a/tests/base.py +++ b/tests/base.py @@ -111,7 +111,7 @@ def setUp(self, mock_verify_calendar_id): quantity=3) resource.save() device = Devices( - last_seen="2018-06-08T11:17:58.785136", + last_seen="2019-04-10T13:28:45", date_added="2018-06-08T11:17:58.785136", name="Samsung", location="Kampala", diff --git a/tests/test_analytics/test_all_analytics_query.py b/tests/test_analytics/test_all_analytics_query.py index 5e5cda620..5c9578a4c 100644 --- a/tests/test_analytics/test_all_analytics_query.py +++ b/tests/test_analytics/test_all_analytics_query.py @@ -8,7 +8,8 @@ all_analytics_query_response, all_analytics_query_invalid_locationid, analytics_query_for_date_ranges, - all_analytics_query_response_super_admin_with_invalid_locationid + all_analytics_query_response_super_admin_with_invalid_locationid, + all_analytics_query_down_time ) @@ -26,6 +27,18 @@ def test_all_analytics_query(self): all_analytics_query_response ) + def test_all_analytics_query_donw(self): + """ + Tests a user can query for analytics + + """ + + CommonTestCases.admin_token_assert_in( + self, + all_analytics_query_down_time, + "this device was seen" + ) + @change_user_role_to_super_admin def test_all_analytics_query_super_admin(self): """