Skip to content

Commit

Permalink
feat: Implement statistics module
Browse files Browse the repository at this point in the history
  • Loading branch information
drorganvidez committed Oct 15, 2024
1 parent ce1edc1 commit 4cde87d
Show file tree
Hide file tree
Showing 19 changed files with 284 additions and 26 deletions.
10 changes: 1 addition & 9 deletions app/modules/dataset/repositories.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import List, Optional

import pytz
from sqlalchemy import desc, func
from sqlalchemy import desc

from app.modules.dataset.models import (
Author,
Expand All @@ -28,10 +28,6 @@ class DSDownloadRecordRepository(BaseRepository):
def __init__(self):
super().__init__(DSDownloadRecord)

def total_dataset_downloads(self) -> int:
max_id = self.model.query.with_entities(func.max(self.model.id)).scalar()
return max_id if max_id is not None else 0

def the_record_exists(self, dataset: DataSet, user_cookie: str):
return self.model.query.filter_by(
user_id=current_user.id if current_user.is_authenticated else None,
Expand Down Expand Up @@ -60,10 +56,6 @@ class DSViewRecordRepository(BaseRepository):
def __init__(self):
super().__init__(DSViewRecord)

def total_dataset_views(self) -> int:
max_id = self.model.query.with_entities(func.max(self.model.id)).scalar()
return max_id if max_id is not None else 0

def the_record_exists(self, dataset: DataSet, user_cookie: str):
return self.model.query.filter_by(
user_id=current_user.id if current_user.is_authenticated else None,
Expand Down
11 changes: 5 additions & 6 deletions app/modules/dataset/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
HubfileRepository,
HubfileViewRecordRepository
)
from app.modules.statistics.services import StatisticsService
from core.services.BaseService import BaseService

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -103,12 +104,6 @@ def count_authors(self) -> int:
def count_dsmetadata(self) -> int:
return self.dsmetadata_repository.count()

def total_dataset_downloads(self) -> int:
return self.dsdownloadrecord_repository.total_dataset_downloads()

def total_dataset_views(self) -> int:
return self.dsviewrecord_repostory.total_dataset_views()

def update_from_form(self, form: DataSetForm, current_user: User, dataset: DataSet) -> DataSet:
main_author = {
"name": f"{current_user.profile.surname}, {current_user.profile.name}",
Expand Down Expand Up @@ -327,6 +322,7 @@ def __init__(self):
class DSDownloadRecordService(BaseService):
def __init__(self):
super().__init__(DSDownloadRecordRepository())
self.statistics_service = StatisticsService()

def the_record_exists(self, dataset: DataSet, user_cookie: str):
return self.repository.the_record_exists(dataset, user_cookie)
Expand All @@ -344,6 +340,7 @@ def create_cookie(self, dataset: DataSet) -> str:

if not existing_record:
self.create_new_record(dataset=dataset, user_cookie=user_cookie)
self.statistics_service.increment_datasets_downloaded()

return user_cookie

Expand All @@ -362,6 +359,7 @@ def filter_by_doi(self, doi: str) -> Optional[DSMetaData]:
class DSViewRecordService(BaseService):
def __init__(self):
super().__init__(DSViewRecordRepository())
self.statistics_service = StatisticsService()

def the_record_exists(self, dataset: DataSet, user_cookie: str):
return self.repository.the_record_exists(dataset, user_cookie)
Expand All @@ -379,6 +377,7 @@ def create_cookie(self, dataset: DataSet) -> str:

if not existing_record:
self.create_new_record(dataset=dataset, user_cookie=user_cookie)
self.statistics_service.increment_datasets_viewed()

return user_cookie

Expand Down
4 changes: 4 additions & 0 deletions app/modules/hubfile/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from app.modules.hubfile.services import HubfileDownloadRecordService, HubfileService

from app import db
from app.modules.statistics.services import StatisticsService

hubfile_download_record_service = HubfileDownloadRecordService()

Expand Down Expand Up @@ -139,6 +140,9 @@ def view_file(file_id):
db.session.add(new_view_record)
db.session.commit()

statistics_service = StatisticsService()
statistics_service.increment_feature_models_viewed()

# Prepare response
response = jsonify({'success': True, 'content': content})
if not request.cookies.get('view_cookie'):
Expand Down
10 changes: 3 additions & 7 deletions app/modules/hubfile/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
HubfileRepository,
HubfileViewRecordRepository
)
from app.modules.statistics.services import StatisticsService
from core.services.BaseService import BaseService


Expand Down Expand Up @@ -40,20 +41,14 @@ def get_path_by_hubfile(self, hubfile: Hubfile) -> str:

return path

def total_hubfile_views(self) -> int:
return self.hubfile_view_record_repository.total_hubfile_views()

def total_hubfile_downloads(self) -> int:
hubfile_download_record_repository = HubfileDownloadRecordRepository()
return hubfile_download_record_repository.total_hubfile_downloads()

def get_by_ids(self, ids: list[int]) -> list[Hubfile]:
return self.repository.get_by_ids(ids)


class HubfileDownloadRecordService(BaseService):
def __init__(self):
super().__init__(HubfileDownloadRecordRepository())
self.statistics_service = StatisticsService()

def the_record_exists(self, hubfile: Hubfile, user_cookie: str):
return self.repository.the_record_exists(hubfile, user_cookie)
Expand All @@ -71,5 +66,6 @@ def create_cookie(self, hubfile: Hubfile):

if not existing_record:
self.create_new_record(hubfile=hubfile, user_cookie=user_cookie)
self.statistics_service.increment_feature_models_downloaded()

return user_cookie
10 changes: 6 additions & 4 deletions app/modules/public/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from app.modules.featuremodel.services import FeatureModelService
from app.modules.public import public_bp
from app.modules.dataset.services import DataSetService
from app.modules.statistics.services import StatisticsService

logger = logging.getLogger(__name__)

Expand All @@ -14,18 +15,19 @@ def index():
logger.info("Access index")
dataset_service = DataSetService()
feature_model_service = FeatureModelService()
statistics_service = StatisticsService()

# Statistics: total datasets and feature models
datasets_counter = dataset_service.count_synchronized_datasets()
feature_models_counter = feature_model_service.count_feature_models()

# Statistics: total downloads
total_dataset_downloads = dataset_service.total_dataset_downloads()
total_feature_model_downloads = feature_model_service.total_feature_model_downloads()
total_dataset_downloads = statistics_service.get_datasets_downloaded()
total_feature_model_downloads = statistics_service.get_feature_models_downloaded()

# Statistics: total views
total_dataset_views = dataset_service.total_dataset_views()
total_feature_model_views = feature_model_service.total_feature_model_views()
total_dataset_views = statistics_service.get_datasets_viewed()
total_feature_model_views = statistics_service.get_feature_models_viewed()

return render_template(
"public/index.html",
Expand Down
3 changes: 3 additions & 0 deletions app/modules/statistics/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from core.blueprints.base_blueprint import BaseBlueprint

statistics_bp = BaseBlueprint('statistics', __name__, template_folder='templates')
1 change: 1 addition & 0 deletions app/modules/statistics/assets/scripts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Hi, I am a script loaded from statistics module");
6 changes: 6 additions & 0 deletions app/modules/statistics/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from flask_wtf import FlaskForm
from wtforms import SubmitField


class StatisticsForm(FlaskForm):
submit = SubmitField('Save statistics')
16 changes: 16 additions & 0 deletions app/modules/statistics/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from app import db


class Statistics(db.Model):
id = db.Column(db.Integer, primary_key=True)
datasets_viewed = db.Column(db.Integer, default=0)
feature_models_viewed = db.Column(db.Integer, default=0)
datasets_downloaded = db.Column(db.Integer, default=0)
feature_models_downloaded = db.Column(db.Integer, default=0)

def __repr__(self):
return (
f'Statistics<id={self.id}, datasets_viewed={self.datasets_viewed}, '
f'feature_models_viewed={self.feature_models_viewed}, datasets_downloaded={self.datasets_downloaded}, '
f'feature_models_downloaded={self.feature_models_downloaded}>'
)
55 changes: 55 additions & 0 deletions app/modules/statistics/repositories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from app.modules.statistics.models import Statistics
from core.repositories.BaseRepository import BaseRepository


class StatisticsRepository(BaseRepository):
def __init__(self):
super().__init__(Statistics)

def get_statistics(self) -> Statistics:
statistics = self.model.query.first()
if statistics is None:
# If no registry exists, create a new registry with default values
statistics = Statistics(datasets_viewed=0, feature_models_viewed=0,
datasets_downloaded=0, feature_models_downloaded=0)
self.session.add(statistics)
self.session.commit()
return statistics

# Incremental methods
def increment_datasets_viewed(self) -> int:
return self._increment_field('datasets_viewed')

def increment_feature_models_viewed(self) -> int:
return self._increment_field('feature_models_viewed')

def increment_datasets_downloaded(self) -> int:
return self._increment_field('datasets_downloaded')

def increment_feature_models_downloaded(self) -> int:
return self._increment_field('feature_models_downloaded')

def _increment_field(self, field_name: str) -> int:
statistics = self.get_statistics()
current_value = getattr(statistics, field_name)
new_value = current_value + 1
setattr(statistics, field_name, new_value)
self.session.commit()
return new_value

# Consultation methods
def get_datasets_viewed(self) -> int:
statistics = self.get_statistics()
return statistics.datasets_viewed

def get_feature_models_viewed(self) -> int:
statistics = self.get_statistics()
return statistics.feature_models_viewed

def get_datasets_downloaded(self) -> int:
statistics = self.get_statistics()
return statistics.datasets_downloaded

def get_feature_models_downloaded(self) -> int:
statistics = self.get_statistics()
return statistics.feature_models_downloaded
7 changes: 7 additions & 0 deletions app/modules/statistics/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from flask import render_template
from app.modules.statistics import statistics_bp


@statistics_bp.route('/statistics', methods=['GET'])
def index():
return render_template('statistics/index.html')
14 changes: 14 additions & 0 deletions app/modules/statistics/seeders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from app.modules.statistics.models import Statistics
from core.seeders.BaseSeeder import BaseSeeder


class StatisticsSeeder(BaseSeeder):

def run(self):

data = [
Statistics(datasets_viewed=0, feature_models_viewed=0,
datasets_downloaded=0, feature_models_downloaded=0)
]

self.seed(data)
37 changes: 37 additions & 0 deletions app/modules/statistics/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from app.modules.statistics.models import Statistics
from app.modules.statistics.repositories import StatisticsRepository
from core.services.BaseService import BaseService


class StatisticsService(BaseService):
def __init__(self):
super().__init__(StatisticsRepository())

def get_statistics(self) -> Statistics:
return self.repository.get_statistics()

# Incremental methods
def increment_datasets_viewed(self) -> int:
return self.repository.increment_datasets_viewed()

def increment_feature_models_viewed(self) -> int:
return self.repository.increment_feature_models_viewed()

def increment_datasets_downloaded(self) -> int:
return self.repository.increment_datasets_downloaded()

def increment_feature_models_downloaded(self) -> int:
return self.repository.increment_feature_models_downloaded()

# Consultation methods
def get_datasets_viewed(self) -> int:
return self.repository.get_datasets_viewed()

def get_feature_models_viewed(self) -> int:
return self.repository.get_feature_models_viewed()

def get_datasets_downloaded(self) -> int:
return self.repository.get_datasets_downloaded()

def get_feature_models_downloaded(self) -> int:
return self.repository.get_feature_models_downloaded()
11 changes: 11 additions & 0 deletions app/modules/statistics/templates/statistics/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% extends "base_template.html" %}

{% block title %}View statistics{% endblock %}

{% block content %}

{% endblock %}

{% block scripts %}
<script src="{{ url_for('statistics.scripts') }}"></script>
{% endblock %}
Empty file.
21 changes: 21 additions & 0 deletions app/modules/statistics/tests/locustfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from locust import HttpUser, TaskSet, task
from core.environment.host import get_host_for_locust_testing


class StatisticsBehavior(TaskSet):
def on_start(self):
self.index()

@task
def index(self):
response = self.client.get("/statistics")

if response.status_code != 200:
print(f"Statistics index failed: {response.status_code}")


class StatisticsUser(HttpUser):
tasks = [StatisticsBehavior]
min_wait = 5000
max_wait = 9000
host = get_host_for_locust_testing()
35 changes: 35 additions & 0 deletions app/modules/statistics/tests/test_selenium.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from selenium.common.exceptions import NoSuchElementException
import time

from core.environment.host import get_host_for_selenium_testing
from core.selenium.common import initialize_driver, close_driver


def test_statistics_index():

driver = initialize_driver()

try:
host = get_host_for_selenium_testing()

# Open the index page
driver.get(f'{host}/statistics')

# Wait a little while to make sure the page has loaded completely
time.sleep(4)

try:

pass

except NoSuchElementException:
raise AssertionError('Test failed!')

finally:

# Close the browser
close_driver(driver)


# Call the test function
test_statistics_index()
Loading

0 comments on commit 4cde87d

Please sign in to comment.