Skip to content
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

Analytics endpoint #504

Merged
merged 9 commits into from
Nov 24, 2023
Merged
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
95 changes: 75 additions & 20 deletions .stoplight/styleguide.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ def create_app(config_class=DevelopmentConfig):

app.register_blueprint(ontology_bp)

from app.analytics import bp as analytics_bp

app.register_blueprint(analytics_bp)

return app


Expand Down
5 changes: 5 additions & 0 deletions app/analytics/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from flask import Blueprint

bp = Blueprint("analytics", __name__)

from app.analytics import routes
30 changes: 30 additions & 0 deletions app/analytics/analytics_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import uuid
from app import db
from app.errors.errors import DatabaseError
from app.models import AnalyticsData
from datetime import datetime, timezone


def log_user_a_event(
session_uuid, category, action, label, event_value, event_timestamp, page_url
):
"""
Log an event in the user a analytics data table.
"""
try:
event_to_add = AnalyticsData()

# event_to_add.event_log_uuid = uuid.uuid4() #this should not be needed as autoincrement is set to true.
event_to_add.session_uuid = session_uuid
event_to_add.category = category
event_to_add.action = action
event_to_add.label = label
event_to_add.value = event_value
event_to_add.event_timestamp = event_timestamp
event_to_add.page_url = page_url
db.session.add(event_to_add)
db.session.commit()
except:
raise DatabaseError(
message="An error occurred while logging a user analytics event."
)
53 changes: 53 additions & 0 deletions app/analytics/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from app.analytics import bp
from app.analytics.analytics_logging import log_user_a_event
from app.common.uuid import validate_uuid, uuidType, check_uuid_in_db
from flask_cors import cross_origin
from flask import request, jsonify

import uuid
from datetime import datetime
from app.analytics.schemas import (
AnalyticsSchema,
)
from app.models import AnalyticsData


@bp.route("/analytics", methods=["POST"])
@cross_origin()
def post_user_a_event():
"""
Logs a user a event in the analytics_data table for analytics tracking.

The required request body must include category, action, label, session_uuid, event_timestamp, value, page_url

Session uuid validation are included for accurate logging.

Parameters
==========
(implicitly session_uuid), category, action, label, session_uuid, event_timestamp, value, page_url

Returns
==========
JSON - success message

"""
session_uuid = request.headers.get("X-Session-Id")
session_uuid = validate_uuid(session_uuid, uuidType.SESSION)
check_uuid_in_db(session_uuid, uuidType.SESSION)

json_data = request.get_json(force=True, silent=True)
schema = AnalyticsSchema()
result_data = schema.load(json_data)
log_user_a_event(
session_uuid,
result_data["category"],
result_data["action"],
result_data["label"],
result_data["event_value"],
result_data["event_timestamp"],
result_data["page_url"],
)

response = {"message": "User event logged."}

return jsonify(response), 201
17 changes: 17 additions & 0 deletions app/analytics/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from marshmallow import (
Schema,
fields,
validates_schema,
ValidationError,
)

from app.common.schemas import CamelCaseSchema


class AnalyticsSchema(CamelCaseSchema, Schema):
category = fields.Str(required=True)
action = fields.Str(required=True)
label = fields.Str(required=True)
event_value = fields.Str(required=True)
event_timestamp = fields.DateTime(required=True)
page_url = fields.Str(required=True)
40 changes: 40 additions & 0 deletions app/analytics/tests/test_analytics_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from datetime import datetime, timedelta, timezone

import pytest
import typing
from flask import url_for
from flask.testing import FlaskClient
from flask_jwt_extended import create_access_token
from freezegun import freeze_time
from mock import mock

from app.factories import (
UsersFactory,
faker,
SessionsFactory,
PasswordResetLinkFactory,
ScoresFactory,
)
from app.models import AnalyticsData, Users


@pytest.mark.integration
def test_add_event(client, accept_json):
session = SessionsFactory()
session_header = [("X-Session-Id", session.session_uuid)]
ok_data = {
"category": faker.pystr(20, 50),
"action": faker.pystr(20, 50),
"label": faker.pystr(20, 50),
"eventValue": faker.pystr(20, 50),
"eventTimestamp": str(faker.date_time()),
"pageUrl": faker.pystr(20, 255),
}

url = url_for("analytics.post_user_a_event")
response = client.post(
url,
headers=session_header,
json=ok_data,
)
assert response.status_code == 201, "User event logged."
4 changes: 3 additions & 1 deletion app/session/tests/test_session_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def test_store_session_creation(
assert Sessions.query.count() == 1, "Single session should be created"
created_session = Sessions.query.first()
assert created_session.session_uuid == session_uuid
assert created_session.session_created_timestamp == session_created_timestamp
db_time = created_session.session_created_timestamp.isoformat(" ", "seconds")
expected_time = session_created_timestamp.isoformat(" ", "seconds")
assert db_time == expected_time
assert created_session.user_uuid == user_uuid
assert created_session.ip_address == ip_address

Expand Down
6 changes: 3 additions & 3 deletions app/session/tests/test_session_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_post_session_creates_unique_uuids(client):
assert (
session.session_uuid == response_uuid
), "The endpoint should return same UUID as stored in DB"
assert (
session.session_created_timestamp == faked_now
), "The session object has been created now"
db_time = session.session_created_timestamp.isoformat(" ", "seconds")
expected_time = faked_now.isoformat(" ", "seconds")
assert db_time == expected_time, "The session object has been created now"
assert session.user_uuid == user.user_uuid, "Mocked user linked"
75 changes: 75 additions & 0 deletions app/static/Climate-Mind_bundled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2016,5 +2016,80 @@ paths:
required:
- email
description: Data required to send password reset link
/analytics:
post:
summary: analytics
tags: []
responses:
'201':
description: Created
content:
application/json:
schema:
type: object
properties:
message:
type: string
x-stoplight:
id: tzuwqo35n2rgq
examples:
OK:
value:
message: User event logged.
operationId: post-analytics
x-stoplight:
id: stuyme02xllm1
parameters:
- schema:
type: string
in: header
name: X-Session-Id
description: Session uuid
description: Logs a user A event in the analytics_data table for analytics tracking.
requestBody:
content:
application/json:
schema:
type: object
properties:
category:
type: string
x-stoplight:
id: pa1jncup2zz7h
action:
type: string
x-stoplight:
id: aweqa3xo9a1z8
label:
type: string
x-stoplight:
id: 8o1e1k2qqqh0y
eventValue:
type: string
x-stoplight:
id: 1ifn6lz1grqpr
eventTimestamp:
type: string
x-stoplight:
id: ifs0snoziugak
format: date-time
pattern: '%Y-%m-%d %H:%M:%S'
pageUrl:
type: string
x-stoplight:
id: y9xlkarog8mdj
examples:
Example 1:
value:
category: landing_page - webapp
action: get_started
label: session_id
eventValue: 4a3d330f-0a87-4e35-a968-bc4218a27dae
eventTimestamp: '2020-11-18 07:07:19'
pageUrl: 'https://app.climatemind.org/'
description: |-
Required fields and information for the analytics event.
eventTimestamp MUST look like the following string format:
%Y-%m-%d %H:%M:%S (ex: 2020-11-18 07:07:19 )
components:
schemas: {}