Skip to content

Commit

Permalink
fix: log phase on experiment level
Browse files Browse the repository at this point in the history
  • Loading branch information
BeritJanssen committed Nov 11, 2024
1 parent a8edec2 commit d744901
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 126 deletions.
32 changes: 17 additions & 15 deletions backend/experiment/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,17 @@ def serialize_phase(phase: Phase, participant: Participant) -> dict:
participant: Participant instance
Returns:
Dashboard info for a participant
A dictionary of the dashboard (if applicable), the next block, and the total score of the phase
"""
blocks = list(Block.objects.filter(phase=phase.id).order_by("index"))
blocks = list(phase.blocks.order_by("index").all())

if phase.randomize:
shuffle(blocks)

next_block = get_upcoming_block(phase, participant, blocks)
next_block = get_upcoming_block(phase, participant)
if not next_block:
return None

total_score = get_total_score(blocks, participant)
if phase.randomize:
shuffle(blocks)

return {
"dashboard": [serialize_block(block, participant) for block in blocks] if phase.dashboard else [],
Expand Down Expand Up @@ -138,17 +137,19 @@ def serialize_block(block_object: Block, language: str = "en") -> dict:
}


def get_upcoming_block(phase: Phase, participant: Participant, block_list: list[Block]):
def get_upcoming_block(phase: Phase, participant: Participant):
"""return next block with minimum finished sessions for this participant
if repeated blocks are not allowed (dashboard=False) and there are only finished sessions, return None
if all blocks have been played an equal number of times, return None
Args:
block_list: List of Block instances
participant: Participant instance
repeat_allowed: Allow repeating a block
phase: Phase for which next block needs to be picked
participant: Participant for which next block needs to be picked
"""
blocks = list(phase.blocks.all())

shuffle(blocks)
finished_session_counts = [
get_finished_session_count(block, participant) for block in block_list
get_finished_session_count(block, participant) for block in blocks
]

min_session_count = min(finished_session_counts)
Expand All @@ -163,7 +164,7 @@ def get_upcoming_block(phase: Phase, participant: Participant, block_list: list[
phase_profile.save()
return None
next_block_index = finished_session_counts.index(min_session_count)
return serialize_block(block_list[next_block_index])
return serialize_block(blocks[next_block_index])


def get_started_session_count(block: Block, participant: Participant) -> int:
Expand Down Expand Up @@ -192,8 +193,9 @@ def get_finished_session_count(block: Block, participant: Participant) -> int:
Number of finished sessions for this block and participant
"""

count = Session.objects.filter(block=block, participant=participant, finished_at__isnull=False).count()
return count
return Session.objects.filter(
block=block, participant=participant, finished_at__isnull=False
).count()


def get_total_score(blocks: list, participant: Participant) -> int:
Expand Down
108 changes: 92 additions & 16 deletions backend/experiment/tests/test_serializers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
from django.conf import settings
from django.test import TestCase
from django.utils import timezone

from experiment.models import Block, Experiment, Phase
from experiment.serializers import serialize_phase
from experiment.models import Block, BlockTranslatedContent, Experiment, Phase
from experiment.serializers import get_upcoming_block, serialize_block, serialize_phase
from experiment.tests.test_views import create_theme_config
from image.models import Image
from participant.models import Participant
from session.models import Session

Expand Down Expand Up @@ -36,22 +39,95 @@ def setUpTestData(cls):
block.save()

def test_serialize_phase(self):
phase = serialize_phase(self.phase1, self.participant)
self.assertIsNotNone(phase)
next_block_slug = phase.get("nextBlock").get("slug")
self.assertEqual(phase.get("dashboard"), [])
self.assertEqual(next_block_slug, "rhythm_intro")
self.assertEqual(phase.get("totalScore"), 0)
Session.objects.create(
participant=self.participant,
block=Block.objects.get(slug=next_block_slug),
finished_at=timezone.now(),
)
phase = serialize_phase(self.phase1, self.participant)
self.assertIsNone(phase)

def test_upcoming_block(self):
block = get_upcoming_block(self.phase1, self.participant)
self.assertEqual(block.get("slug"), "rhythm_intro")
Session.objects.create(
block=Block.objects.get(slug=block.get("slug")),
participant=self.participant,
finished_at=timezone.now(),
)
block = get_upcoming_block(self.phase1, self.participant)
self.assertIsNone(block)
for i in range(3):
phase = serialize_phase(self.phase2, self.participant)
next_block = phase.get("nextBlock")
self.assertIsNotNone(next_block)
self.assertIn(next_block.get("slug"), ["ddi", "hbat_bit", "rhdis"])
block_obj = Block.objects.get(slug=next_block.get("slug"))
block = get_upcoming_block(self.phase2, self.participant)
self.assertIsNotNone(block)
self.assertIn(block.get("slug"), ["ddi", "hbat_bit", "rhdis"])
Session.objects.create(
block=block_obj,
block=Block.objects.get(slug=block.get("slug")),
participant=self.participant,
finished_at=timezone.now(),
)
phase = serialize_phase(self.phase2, self.participant)
self.assertIsNone(phase)
# if we enter the phase once more, we're going to get next_block again
phase = serialize_phase(self.phase2, self.participant)
self.assertIsNotNone(phase)
next_block = phase.get("nextBlock")
self.assertIsNotNone(next_block)
self.assertIn(next_block.get("slug"), ["ddi", "hbat_bit", "rhdis"])
block = get_upcoming_block(self.phase2, self.participant)
self.assertIsNone(block)

def test_serialize_block(self):
# Create the experiment & phase for the block
experiment = Experiment.objects.create(slug="test-experiment")
phase = Phase.objects.create(experiment=experiment)

# Create a block
block = Block.objects.create(
slug="test-block",
image=Image.objects.create(
title="Test",
description="",
file="test-image.jpg",
alt="Test",
href="https://www.example.com",
rel="",
target="_self",
),
theme_config=create_theme_config(),
phase=phase,
)
BlockTranslatedContent.objects.create(
block=block,
language="en",
name="Test Block",
description="This is a test block",
)
participant = Participant.objects.create()
Session.objects.bulk_create(
[
Session(
block=block, participant=participant, finished_at=timezone.now()
)
for index in range(3)
]
)

# Call the serialize_block function
serialized_block = serialize_block(block, participant)

# Assert the serialized data
self.assertEqual(serialized_block["slug"], "test-block")
self.assertEqual(serialized_block["name"], "Test Block")
self.assertEqual(serialized_block["description"], "This is a test block")
self.assertEqual(
serialized_block["image"],
{
"title": "Test",
"description": "",
"file": f"{settings.BASE_URL}/upload/test-image.jpg",
"href": "https://www.example.com",
"alt": "Test",
"rel": "",
"target": "_self",
"tags": [],
},
)
99 changes: 20 additions & 79 deletions backend/experiment/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from django.conf import settings
from django.test import TestCase
from django.utils import timezone
from django.core.files.uploadedfile import SimpleUploadedFile

from image.models import Image
from experiment.serializers import serialize_block, serialize_phase
from experiment.serializers import serialize_phase
from experiment.models import (
Block,
BlockTranslatedContent,
Expand All @@ -24,6 +23,9 @@ class TestExperimentViews(TestCase):
@classmethod
def setUpTestData(cls):
cls.participant = Participant.objects.create()
session = cls.client.session
session["participant_id"] = cls.participant.id
session.save()
theme_config = create_theme_config()
experiment = Experiment.objects.create(
slug="test_series",
Expand All @@ -46,10 +48,6 @@ def setUpTestData(cls):
cls.block4 = Block.objects.create(slug="block4", phase=cls.final_phase)

def test_get_experiment(self):
# save participant data to request session
session = self.client.session
session["participant_id"] = self.participant.id
session.save()
# check that the correct block is returned correctly
response = self.client.get("/experiment/test_series/")
self.assertEqual(response.json().get("nextBlock").get("slug"), "block1")
Expand All @@ -72,45 +70,43 @@ def test_get_experiment(self):
self.assertEqual(response_json.get("socialMedia").get("tags"), ["aml", "toontjehoger"])
self.assertEqual(response_json.get("socialMedia").get("channels"), ["facebook", "twitter", "weibo"])

def test_get_experiment_not_found(self):
def test_experiment_not_found(self):
# if Experiment does not exist, return 404
response = self.client.get("/experiment/not_found/")
self.assertEqual(response.status_code, 404)

def test_get_experiment_inactive(self):
def test_experiment_inactive(self):
# if Experiment is inactive, return 404
experiment = Experiment.objects.get(slug="test_series")
experiment.active = False
experiment.save()
response = self.client.get("/experiment/test_series/")
self.assertEqual(response.status_code, 404)

def test_get_experiment_without_social_media(self):
session = self.client.session
session["participant_id"] = self.participant.id
session.save()
Session.objects.create(block=self.block1, participant=self.participant, finished_at=timezone.now())
self.intermediate_phase.dashboard = True
self.intermediate_phase.save()
def test_experiment_has_no_phases(self):
Experiment.objects.create(slug="invalid_experiment")
response = self.client.get("/experiment/invalid_experiment/")
self.assertEqual(response.status_code, 500)

def test_experiment_without_social_media(self):
experiment = Experiment.objects.create(
slug="no_social_media",
theme_config=create_theme_config(name="no_social_media"),
)
self.intermediate_phase.experiment = experiment
self.intermediate_phase.save()
ExperimentTranslatedContent.objects.create(
experiment=experiment, language="en", name="Test Experiment", description="Test Description"
experiment=experiment,
language="en",
name="Test Experiment",
description="Test Description",
)

response = self.client.get("/experiment/no_social_media/")

self.assertEqual(response.status_code, 200)
self.assertNotIn("socialMedia", response.json())

def test_experiment_with_dashboard(self):
# if Experiment has dashboard set True, return list of random blocks
session = self.client.session
session["participant_id"] = self.participant.id
session.save()
Session.objects.create(block=self.block1, participant=self.participant, finished_at=timezone.now())
self.intermediate_phase.dashboard = True
self.intermediate_phase.save()
Expand All @@ -120,9 +116,6 @@ def test_experiment_with_dashboard(self):

def test_experiment_total_score(self):
"""Test calculation of total score for grouped block on dashboard"""
session = self.client.session
session["participant_id"] = self.participant.id
session.save()
Session.objects.create(
block=self.block2, participant=self.participant, finished_at=timezone.now(), final_score=8
)
Expand All @@ -148,6 +141,8 @@ def test_experiment_get_fallback_content(self):
"""Test get_fallback_content method"""

experiment = Experiment.objects.create(slug="test_experiment_translated_content")
self.intermediate_phase.experiment = experiment
self.intermediate_phase.save()
ExperimentTranslatedContent.objects.create(
experiment=experiment,
index=0,
Expand Down Expand Up @@ -185,61 +180,7 @@ def test_experiment_get_fallback_content(self):
response = self.client.get("/experiment/test_experiment_translated_content/", headers={"Accept-Language": "nl"})

# since no Dutch translation is available, the fallback content should be returned
self.assertEqual(response.json().get("name"), "Test Experiment Fallback Content")


class ExperimentViewsTest(TestCase):
def test_serialize_block(self):
# Create the experiment & phase for the block
experiment = Experiment.objects.create(slug="test-experiment")
phase = Phase.objects.create(experiment=experiment)

# Create a block
block = Block.objects.create(
slug="test-block",
image=Image.objects.create(
title="Test",
description="",
file="test-image.jpg",
alt="Test",
href="https://www.example.com",
rel="",
target="_self",
),
theme_config=create_theme_config(),
phase=phase,
)
BlockTranslatedContent.objects.create(
block=block,
language="en",
name="Test Block",
description="This is a test block",
)
participant = Participant.objects.create()
Session.objects.bulk_create(
[Session(block=block, participant=participant, finished_at=timezone.now()) for index in range(3)]
)

# Call the serialize_block function
serialized_block = serialize_block(block, participant)

# Assert the serialized data
self.assertEqual(serialized_block["slug"], "test-block")
self.assertEqual(serialized_block["name"], "Test Block")
self.assertEqual(serialized_block["description"], "This is a test block")
self.assertEqual(
serialized_block["image"],
{
"title": "Test",
"description": "",
"file": f"{settings.BASE_URL}/upload/test-image.jpg",
"href": "https://www.example.com",
"alt": "Test",
"rel": "",
"target": "_self",
"tags": [],
},
)
self.assertEqual(response.json().get("name"), "Test Experiment Fallback Content")=å

def test_get_block(self):
# Create a block
Expand Down
Loading

0 comments on commit d744901

Please sign in to comment.