Repository: fossasia/open-event-server

Issue:
Implement general event analytics API
Implement event analytics API for the event organizer dashboard, 
General bulk data about event should be provided by event id/identifier

@niranjan94 Please suggest Url and required fields for this one issue only, any other issues may follow the same pattern


Discussion:
@niranjan94 Can you please have a look at this :smile: 
May I suggest some general fields we have to calculate for the Android Orga App?

- Total Tickets per type
- Sold Tickets per type
- Checked in Attendees
- Total Attendees
- Total Sales and by Type

Resolving patch:
diff --git a/app/api/__init__.py b/app/api/__init__.py
--- a/app/api/__init__.py
+++ b/app/api/__init__.py
@@ -48,6 +48,7 @@
 from app.api.custom_placeholders import CustomPlaceholderList, CustomPlaceholderDetail, CustomPlaceholderRelationship
 from app.api.activities import ActivityList, ActivityDetail
 from app.api.orders import OrdersList, OrderDetail, OrderRelationship, ChargeList, OrdersListPost
+from app.api.event_statistics import EventStatisticsGeneralDetail
 
 # users
 api.route(UserList, 'user_list', '/users')
@@ -386,11 +387,13 @@
 api.route(StripeAuthorizationRelationship, 'stripe_authorization_event',
           '/stripe-authorization/<int:id>/relationships/event')
 
+
 # Orders API
 api.route(OrdersListPost, 'order_list_post', '/orders')
 api.route(OrdersList, 'orders_list', '/orders', '/events/<int:event_id>/orders',
           '/events/<event_identifier>/orders')
 api.route(OrderDetail, 'order_detail', '/orders/<identifier>')
+
 # Charges API
 api.route(ChargeList, 'charge_list', '/orders/<identifier>/charge', '/orders/<identifier>/charge')
 api.route(OrderRelationship, 'order_attendee', '/orders/<identifier>/relationships/attendee')
@@ -399,3 +402,7 @@
 api.route(OrderRelationship, 'order_event', '/orders/<identifier>/relationships/event')
 api.route(OrderRelationship, 'order_marketer', '/orders/<identifier>/relationships/marketer')
 api.route(OrderRelationship, 'order_discount', '/orders/<identifier>/relationships/discount-code')
+
+# Event Statistics API
+api.route(EventStatisticsGeneralDetail, 'event_statistics_general_detail', '/events/<int:id>/general-statistics',
+          '/events/<identifier>/general-statistics')
diff --git a/app/api/event_statistics.py b/app/api/event_statistics.py
new file mode 100644
--- /dev/null
+++ b/app/api/event_statistics.py
@@ -0,0 +1,84 @@
+from flask_rest_jsonapi import ResourceDetail
+from marshmallow_jsonapi.flask import Schema
+from marshmallow_jsonapi import fields
+
+from app.api.helpers.utilities import dasherize
+from app.api.helpers.db import safe_query
+from app.api.bootstrap import api
+from app.models import db
+from app.models.event import Event
+from app.models.session import Session
+from app.models.speaker import Speaker
+from app.models.sponsor import Sponsor
+
+
+class EventStatisticsGeneralSchema(Schema):
+    """
+    Api schema for general statistics of event
+    """
+    class Meta:
+        """
+        Meta class
+        """
+        type_ = 'event-statistics-general'
+        self_view = 'v1.event_statistics_general_detail'
+        self_view_kwargs = {'id': '<id>'}
+        inflect = dasherize
+
+    id = fields.Str()
+    identifier = fields.Str()
+    sessions_draft = fields.Method("sessions_draft_count")
+    sessions_submitted = fields.Method("sessions_submitted_count")
+    sessions_accepted = fields.Method("sessions_accepted_count")
+    sessions_confirmed = fields.Method("sessions_confirmed_count")
+    sessions_pending = fields.Method("sessions_pending_count")
+    sessions_rejected = fields.Method("sessions_rejected_count")
+    speakers = fields.Method("speakers_count")
+    sessions = fields.Method("sessions_count")
+    sponsors = fields.Method("sponsors_count")
+
+    def sessions_draft_count(self, obj):
+        return Session.query.filter_by(event_id=obj.id, state='draft').count()
+
+    def sessions_submitted_count(self, obj):
+        return Session.query.filter_by(event_id=obj.id, state='submitted').count()
+
+    def sessions_accepted_count(self, obj):
+        return Session.query.filter_by(event_id=obj.id, state='accepted').count()
+
+    def sessions_confirmed_count(self, obj):
+        return Session.query.filter_by(event_id=obj.id, state='confirmed').count()
+
+    def sessions_pending_count(self, obj):
+        return Session.query.filter_by(event_id=obj.id, state='pending').count()
+
+    def sessions_rejected_count(self, obj):
+        return Session.query.filter_by(event_id=obj.id, state='rejected').count()
+
+    def speakers_count(self, obj):
+        return Speaker.query.filter_by(event_id=obj.id).count()
+
+    def sessions_count(self, obj):
+        return Session.query.filter_by(event_id=obj.id).count()
+
+    def sponsors_count(self, obj):
+        return Sponsor.query.filter_by(event_id=obj.id).count()
+
+
+class EventStatisticsGeneralDetail(ResourceDetail):
+    """
+    Event statistics detail by id
+    """
+    def before_get_object(self, view_kwargs):
+        if view_kwargs.get('identifier'):
+            event = safe_query(self, Event, 'identifier', view_kwargs['identifier'], 'identifier')
+            view_kwargs['id'] = event.id
+
+    methods = ['GET']
+    decorators = (api.has_permission('is_coorganizer', fetch="id", fetch_as="event_id", model=Event),)
+    schema = EventStatisticsGeneralSchema
+    data_layer = {'session': db.session,
+                  'model': Event,
+                  'methods': {
+                      'before_get_object': before_get_object
+                  }}


Test patch:
diff --git a/tests/hook_main.py b/tests/hook_main.py
--- a/tests/hook_main.py
+++ b/tests/hook_main.py
@@ -2900,3 +2900,18 @@ def stripe_authorization_delete(transaction):
         stripe = StripeAuthorizationFactory()
         db.session.add(stripe)
         db.session.commit()
+
+
+# ------------------------- Event Statistics -------------------------
+
+@hooks.before("Event Statistics > Event Statistics Details > Show Event Statistics General")
+def event_statistics_general_get(transaction):
+    """
+    GET /events/1/general-statistics
+    :param transaction:
+    :return:
+    """
+    with stash['app'].app_context():
+        event = EventFactoryBasic()
+        db.session.add(event)
+        db.session.commit()