Skip to content

Commit

Permalink
chore: extra status fields
Browse files Browse the repository at this point in the history
  • Loading branch information
francisvaut committed May 23, 2024
1 parent 4270777 commit d6eb2d8
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 54 deletions.
75 changes: 39 additions & 36 deletions backend/api/serializers/project_serializer.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
from django.core.files.uploadedfile import InMemoryUploadedFile

from api.logic.parse_zip_files import parse_zip
from api.models.group import Group
from api.models.project import Project
from api.models.submission import Submission, ExtraCheckResult, StructureCheckResult, StateEnum
from api.models.checks import ExtraCheck, StructureCheck
from api.serializers.course_serializer import CourseSerializer
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.utils import timezone
from django.utils.translation import gettext
from nh3 import clean
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from api.serializers.fields.expandable_hyperlinked_field import ExpandableHyperlinkedIdentityField


class SubmissionStatusSerializer(serializers.Serializer):
non_empty_groups = serializers.IntegerField(read_only=True)
Expand All @@ -28,18 +25,25 @@ def to_representation(self, instance: Project):

non_empty_groups = Group.objects.filter(project=instance, students__isnull=False).distinct().count()

# groups_submitted
groups_submitted_ids = Submission.objects.filter(group__project=instance).values_list('group__id', flat=True)
unique_groups = set(groups_submitted_ids)
groups_submitted = len(unique_groups)

# The total amount of groups with at least one submission should never exceed the total number of non empty groups
# (the seeder does not account for this restriction)
if groups_submitted > non_empty_groups:
if (groups_submitted > non_empty_groups):
non_empty_groups = groups_submitted

extra_checks_count = instance.extra_checks.count()
# has_structure_checks
has_structure_checks = instance.structure_checks.count() != 0

# has_extra checks
has_extra_checks = instance.extra_checks.count() != 0

if extra_checks_count:
# extra_checks_passed: only calculate if the project actually contains extra checks
extra_checks_passed = 0
if has_extra_checks:
passed_extra_checks_submission_ids = ExtraCheckResult.objects.filter(
submission__group__project=instance,
submission__is_valid=True,
Expand All @@ -53,42 +57,41 @@ def to_representation(self, instance: Project):
unique_groups = set(passed_extra_checks_group_ids)
extra_checks_passed = len(unique_groups)

passed_structure_checks_submission_ids = StructureCheckResult.objects.filter(
submission__group__project=instance,
submission__is_valid=True,
result=StateEnum.SUCCESS
).values_list('submission__id', flat=True)

passed_structure_checks_group_ids = Submission.objects.filter(
id__in=passed_structure_checks_submission_ids
).values_list('group_id', flat=True)

unique_groups = set(passed_structure_checks_group_ids)
structure_checks_passed = len(unique_groups)
# structure_checks_passed: only calculate if the project actually contains structure checks
structure_checks_passed = 0
if has_structure_checks:
passed_structure_checks_submission_ids = StructureCheckResult.objects.filter(
submission__group__project=instance,
submission__is_valid=True,
result=StateEnum.SUCCESS
).values_list('submission__id', flat=True)

# If there are no extra checks, we can set extra_checks_passed equal to structure_checks_passed
if not extra_checks_count:
extra_checks_passed = structure_checks_passed
passed_structure_checks_group_ids = Submission.objects.filter(
id__in=passed_structure_checks_submission_ids
).values_list('group_id', flat=True)

# If the extra checks succeed, the structure checks also succeed
structure_checks_passed -= extra_checks_passed
unique_groups = set(passed_structure_checks_group_ids)
structure_checks_passed = len(unique_groups)

# The total number of passed extra checks combined with the number of passed structure checks
# can never exceed the total number of submissions (the seeder does not account for this restriction)
if structure_checks_passed + extra_checks_passed > groups_submitted:
extra_checks_passed = groups_submitted - structure_checks_passed
# We can assume that if the extra checks pass, the struture checks pass as well
if (structure_checks_passed < extra_checks_passed):
structure_checks_passed = extra_checks_passed

return {
"non_empty_groups": non_empty_groups,
"groups_submitted": groups_submitted,
"has_structure_checks": has_structure_checks,
"has_extra_checks": has_extra_checks,
"structure_checks_passed": structure_checks_passed,
"extra_checks_passed": extra_checks_passed
"extra_checks_passed": extra_checks_passed,
}

class Meta:
fields = [
"non_empty_groups",
"groups_submitted",
"has_structure_checks",
"has_extra_checks",
"structure_checks_passed",
"extra_checks_passed"
]
Expand All @@ -107,11 +110,13 @@ class ProjectSerializer(serializers.ModelSerializer):
)

structure_checks = serializers.HyperlinkedIdentityField(
view_name="project-structure-checks"
view_name="project-structure-checks",
read_only=True
)

extra_checks = serializers.HyperlinkedIdentityField(
view_name="project-extra-checks"
view_name="project-extra-checks",
read_only=True
)

groups = serializers.HyperlinkedIdentityField(
Expand Down Expand Up @@ -158,8 +163,6 @@ def validate(self, attrs):
return attrs

def create(self, validated_data):
"""Create the project object and create groups for the project if specified"""

# Pop the 'number_groups' field from validated_data
number_groups = validated_data.pop('number_groups', None)

Expand All @@ -176,6 +179,7 @@ def create(self, validated_data):
group.students.add(student)

elif number_groups:

for _ in range(number_groups):
Group.objects.create(project=project)

Expand All @@ -185,11 +189,10 @@ def create(self, validated_data):
group_size = project.group_size

for _ in range(0, number_students, group_size):
Group.objects.create(project=project)
group = Group.objects.create(project=project)

# If a zip_structure is provided, parse it to create the structure checks
zip_structure: InMemoryUploadedFile | None = self.context['request'].FILES.get('zip_structure')

if zip_structure:
result = parse_zip(project, zip_structure)

Expand All @@ -211,4 +214,4 @@ class CreateProjectSerializer(ProjectSerializer):


class TeacherCreateGroupSerializer(serializers.Serializer):
number_groups = serializers.IntegerField(min_value=1)
number_groups = serializers.IntegerField(min_value=1)
51 changes: 33 additions & 18 deletions frontend/src/components/submissions/ProjectMeter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,45 @@ const { t } = useI18n();
const meterItems = computed(() => {
const groups = props.project !== null ? props.project.status.non_empty_groups : 0;
const groupsSubmitted = props.project !== null ? props.project.status.groups_submitted : 0;
const structureChecksPassed = props.project !== null ? props.project.status.structure_checks_passed : 0;
const hasStructurechecks = props.project !== null ? props.project.status.has_structure_checks : false;
const hasExtraChecks = props.project !== null ? props.project.status.has_extra_checks : false;
const extraChecksPassed = props.project !== null ? props.project.status.extra_checks_passed : 0;
const structureChecksPassed = props.project !== null ? props.project.status.structure_checks_passed : 0;
const submissionsFailed = groupsSubmitted - structureChecksPassed;
return [
{
value: (extraChecksPassed / groups) * 100,
color: '#8fb682',
label: t('components.card.extraTestsSucceed'),
icon: 'pi pi-check',
},
{
value: ((structureChecksPassed - extraChecksPassed) / groups) * 100,
color: '#FFB84F',
label: t('components.card.structureTestsSucceed'),
icon: 'pi pi-exclamation-circle',
},
{
const extraChecksFailed = structureChecksPassed - extraChecksPassed;
const green = '#76DD78'
const orange = '#FFB84F'
const red = '#F37142'
const submissionsFailedItem = {
value: (submissionsFailed / groups) * 100,
color: 'indianred',
color: red,
label: t('components.card.testsFail'),
icon: 'pi pi-times',
},
];
}
const structureChecksPassedItem = {
value: (extraChecksFailed / groups) * 100,
color: orange,
label: t('components.card.structureTestsSucceed'),
icon: 'pi pi-exclamation-circle',
}
const extraChecksPassedItem = {
value: (extraChecksPassed / groups) * 100,
color: green,
label: t('components.card.extraTestsSucceed'),
icon: 'pi pi-check',
}
if (hasStructurechecks) {
if (hasExtraChecks) {
return [submissionsFailedItem, structureChecksPassedItem, extraChecksPassedItem]
}
structureChecksPassedItem.color = green
return [submissionsFailedItem, structureChecksPassedItem]
}
return [{ value: (groupsSubmitted / groups) * 100, color: green, label: t('components.card.testsFail'), icon: 'pi pi-times' }]
});
</script>

Expand Down
6 changes: 6 additions & 0 deletions frontend/src/types/SubmisionStatus.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export interface SubmissionStatusJSON {
non_empty_groups: number;
groups_submitted: number;
has_structure_checks: boolean;
has_extra_checks: boolean;
structure_checks_passed: number;
extra_checks_passed: number;
}
Expand All @@ -9,6 +11,8 @@ export class SubmissionStatus {
constructor(
public non_empty_groups: number = 0,
public groups_submitted: number = 0,
public has_structure_checks: boolean = false,
public has_extra_checks: boolean = false,
public structure_checks_passed: number = 0,
public extra_checks_passed: number = 0,
) {}
Expand All @@ -22,6 +26,8 @@ export class SubmissionStatus {
return new SubmissionStatus(
submissionStatus.non_empty_groups,
submissionStatus.groups_submitted,
submissionStatus.has_structure_checks,
submissionStatus.has_extra_checks,
submissionStatus.structure_checks_passed,
submissionStatus.extra_checks_passed,
);
Expand Down

0 comments on commit d6eb2d8

Please sign in to comment.