From b695f02710aa300b8cc4534316486f342392c53f Mon Sep 17 00:00:00 2001 From: francis Date: Mon, 13 May 2024 23:08:44 +0200 Subject: [PATCH 1/5] chore: better submission status --- backend/api/serializers/project_serializer.py | 55 ++++++++++++++++--- frontend/src/assets/lang/app/en.json | 3 +- frontend/src/assets/lang/app/nl.json | 3 +- .../src/components/projects/ProjectMeter.vue | 16 ++++-- frontend/src/types/SubmisionStatus.ts | 6 +- 5 files changed, 68 insertions(+), 15 deletions(-) diff --git a/backend/api/serializers/project_serializer.py b/backend/api/serializers/project_serializer.py index 274d4e55..b4493229 100644 --- a/backend/api/serializers/project_serializer.py +++ b/backend/api/serializers/project_serializer.py @@ -1,7 +1,7 @@ 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 +from api.models.submission import Submission, ExtraCheckResult, StructureCheckResult, StateEnum from api.serializers.course_serializer import CourseSerializer from django.core.files.uploadedfile import InMemoryUploadedFile from django.utils import timezone @@ -14,28 +14,69 @@ class SubmissionStatusSerializer(serializers.Serializer): non_empty_groups = serializers.IntegerField(read_only=True) groups_submitted = serializers.IntegerField(read_only=True) - submissions_passed = serializers.IntegerField(read_only=True) + structure_checks_passed = serializers.IntegerField(read_only=True) + extra_checks_passed = serializers.IntegerField(read_only=True) def to_representation(self, instance: Project): """Return the submission status of the project""" if not isinstance(instance, Project): raise ValidationError(gettext("project.errors.invalid_instance")) - non_empty_groups = instance.groups.filter(students__isnull=False).count() - groups_submitted = Submission.objects.filter(group__project=instance).count() - submissions_passed = Submission.objects.filter(group__project=instance, is_valid=True).count() + non_empty_groups = Group.objects.filter(project=instance, students__isnull=False).distinct().count() + + 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): + non_empty_groups = groups_submitted + + 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) + + passed_extra_checks_submission_ids = ExtraCheckResult.objects.filter( + submission__group__project=instance, + submission__is_valid=True, + result=StateEnum.SUCCESS + ).values_list('submission__id', flat=True) + + passed_extra_checks_group_ids = Submission.objects.filter( + id__in=passed_extra_checks_submission_ids + ).values_list('group_id', flat=True) + + unique_groups = set(passed_extra_checks_group_ids) + extra_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 return { "non_empty_groups": non_empty_groups, "groups_submitted": groups_submitted, - "submissions_passed": submissions_passed, + "structure_checks_passed": structure_checks_passed, + "extra_checks_passed": extra_checks_passed } class Meta: fields = [ "non_empty_groups", "groups_submitted", - "submissions_passed", + "structure_checks_passed", + "extra_checks_passed" ] diff --git a/frontend/src/assets/lang/app/en.json b/frontend/src/assets/lang/app/en.json index a76b97fc..bafc4d00 100644 --- a/frontend/src/assets/lang/app/en.json +++ b/frontend/src/assets/lang/app/en.json @@ -186,7 +186,8 @@ "noSubmissions": "This project does not have any submissions", "submissions": "Submission | Submissions", "groups": "Group | Groups", - "testsSucceed": "Succeeded tests", + "structureTestsSucceed": "Succeeded structure tests", + "extraTestsSucceed": "Succeeded extra tests", "testsFail": "Failed tests", "submit": "Submit" }, diff --git a/frontend/src/assets/lang/app/nl.json b/frontend/src/assets/lang/app/nl.json index f403b20d..7864c171 100644 --- a/frontend/src/assets/lang/app/nl.json +++ b/frontend/src/assets/lang/app/nl.json @@ -183,7 +183,8 @@ "noSubmissions": "Dit project heeft geen indieningen", "submissions": "Indiening | Indieningen", "groups": "Groep | Groepen", - "testsSucceed": "Geslaagde testen", + "structureTestsSucceed": "Geslaagde structuur testen", + "extraTestsSucceed": "Geslaagde extra testen", "testsFail": "Gefaalde testen", "submit": "Indienen" }, diff --git a/frontend/src/components/projects/ProjectMeter.vue b/frontend/src/components/projects/ProjectMeter.vue index 823dd351..c02fc00c 100644 --- a/frontend/src/components/projects/ProjectMeter.vue +++ b/frontend/src/components/projects/ProjectMeter.vue @@ -17,15 +17,23 @@ 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 submissionsPassed = props.project !== null ? props.project.status.submissions_passed : 0; - const submissionsFailed = groupsSubmitted - submissionsPassed; + const structureChecksPassed = props.project !== null ? props.project.status.structure_checks_passed : 0; + const extraChecksPassed = props.project !== null ? props.project.status.extra_checks_passed : 0; + const submissionsFailed = groupsSubmitted - structureChecksPassed; + return [ { - value: (submissionsPassed / groups) * 100, + value: (extraChecksPassed / groups) * 100, color: '#749b68', - label: t('components.card.testsSucceed'), + label: t('components.card.extraTestsSucceed'), icon: 'pi pi-check', }, + { + value: (structureChecksPassed / groups) * 100, + color: '#fa9746', + label: t('components.card.structureTestsSucceed'), + icon: 'pi pi-exclamation-circle', + }, { value: (submissionsFailed / groups) * 100, color: '#FF5445', diff --git a/frontend/src/types/SubmisionStatus.ts b/frontend/src/types/SubmisionStatus.ts index 159b1129..597bbece 100644 --- a/frontend/src/types/SubmisionStatus.ts +++ b/frontend/src/types/SubmisionStatus.ts @@ -2,7 +2,8 @@ export class SubmissionStatus { constructor( public non_empty_groups: number, public groups_submitted: number, - public submissions_passed: number, + public structure_checks_passed: number, + public extra_checks_passed: number, ) {} /** @@ -14,7 +15,8 @@ export class SubmissionStatus { return new SubmissionStatus( submissionStatus.non_empty_groups, submissionStatus.groups_submitted, - submissionStatus.submissions_passed, + submissionStatus.structure_checks_passed, + submissionStatus.extra_checks_passed, ); } } From 33d118ded5a163d3c8feeb1178239b5c338fa0b6 Mon Sep 17 00:00:00 2001 From: francis Date: Mon, 13 May 2024 23:09:37 +0200 Subject: [PATCH 2/5] Revert "chore: better submission status" This reverts commit b695f02710aa300b8cc4534316486f342392c53f. --- backend/api/serializers/project_serializer.py | 55 +++---------------- frontend/src/assets/lang/app/en.json | 3 +- frontend/src/assets/lang/app/nl.json | 3 +- .../src/components/projects/ProjectMeter.vue | 16 ++---- frontend/src/types/SubmisionStatus.ts | 6 +- 5 files changed, 15 insertions(+), 68 deletions(-) diff --git a/backend/api/serializers/project_serializer.py b/backend/api/serializers/project_serializer.py index b4493229..274d4e55 100644 --- a/backend/api/serializers/project_serializer.py +++ b/backend/api/serializers/project_serializer.py @@ -1,7 +1,7 @@ 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.submission import Submission from api.serializers.course_serializer import CourseSerializer from django.core.files.uploadedfile import InMemoryUploadedFile from django.utils import timezone @@ -14,69 +14,28 @@ class SubmissionStatusSerializer(serializers.Serializer): non_empty_groups = serializers.IntegerField(read_only=True) groups_submitted = serializers.IntegerField(read_only=True) - structure_checks_passed = serializers.IntegerField(read_only=True) - extra_checks_passed = serializers.IntegerField(read_only=True) + submissions_passed = serializers.IntegerField(read_only=True) def to_representation(self, instance: Project): """Return the submission status of the project""" if not isinstance(instance, Project): raise ValidationError(gettext("project.errors.invalid_instance")) - non_empty_groups = Group.objects.filter(project=instance, students__isnull=False).distinct().count() - - 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): - non_empty_groups = groups_submitted - - 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) - - passed_extra_checks_submission_ids = ExtraCheckResult.objects.filter( - submission__group__project=instance, - submission__is_valid=True, - result=StateEnum.SUCCESS - ).values_list('submission__id', flat=True) - - passed_extra_checks_group_ids = Submission.objects.filter( - id__in=passed_extra_checks_submission_ids - ).values_list('group_id', flat=True) - - unique_groups = set(passed_extra_checks_group_ids) - extra_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 + non_empty_groups = instance.groups.filter(students__isnull=False).count() + groups_submitted = Submission.objects.filter(group__project=instance).count() + submissions_passed = Submission.objects.filter(group__project=instance, is_valid=True).count() return { "non_empty_groups": non_empty_groups, "groups_submitted": groups_submitted, - "structure_checks_passed": structure_checks_passed, - "extra_checks_passed": extra_checks_passed + "submissions_passed": submissions_passed, } class Meta: fields = [ "non_empty_groups", "groups_submitted", - "structure_checks_passed", - "extra_checks_passed" + "submissions_passed", ] diff --git a/frontend/src/assets/lang/app/en.json b/frontend/src/assets/lang/app/en.json index bafc4d00..a76b97fc 100644 --- a/frontend/src/assets/lang/app/en.json +++ b/frontend/src/assets/lang/app/en.json @@ -186,8 +186,7 @@ "noSubmissions": "This project does not have any submissions", "submissions": "Submission | Submissions", "groups": "Group | Groups", - "structureTestsSucceed": "Succeeded structure tests", - "extraTestsSucceed": "Succeeded extra tests", + "testsSucceed": "Succeeded tests", "testsFail": "Failed tests", "submit": "Submit" }, diff --git a/frontend/src/assets/lang/app/nl.json b/frontend/src/assets/lang/app/nl.json index 7864c171..f403b20d 100644 --- a/frontend/src/assets/lang/app/nl.json +++ b/frontend/src/assets/lang/app/nl.json @@ -183,8 +183,7 @@ "noSubmissions": "Dit project heeft geen indieningen", "submissions": "Indiening | Indieningen", "groups": "Groep | Groepen", - "structureTestsSucceed": "Geslaagde structuur testen", - "extraTestsSucceed": "Geslaagde extra testen", + "testsSucceed": "Geslaagde testen", "testsFail": "Gefaalde testen", "submit": "Indienen" }, diff --git a/frontend/src/components/projects/ProjectMeter.vue b/frontend/src/components/projects/ProjectMeter.vue index c02fc00c..823dd351 100644 --- a/frontend/src/components/projects/ProjectMeter.vue +++ b/frontend/src/components/projects/ProjectMeter.vue @@ -17,23 +17,15 @@ 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 extraChecksPassed = props.project !== null ? props.project.status.extra_checks_passed : 0; - const submissionsFailed = groupsSubmitted - structureChecksPassed; - + const submissionsPassed = props.project !== null ? props.project.status.submissions_passed : 0; + const submissionsFailed = groupsSubmitted - submissionsPassed; return [ { - value: (extraChecksPassed / groups) * 100, + value: (submissionsPassed / groups) * 100, color: '#749b68', - label: t('components.card.extraTestsSucceed'), + label: t('components.card.testsSucceed'), icon: 'pi pi-check', }, - { - value: (structureChecksPassed / groups) * 100, - color: '#fa9746', - label: t('components.card.structureTestsSucceed'), - icon: 'pi pi-exclamation-circle', - }, { value: (submissionsFailed / groups) * 100, color: '#FF5445', diff --git a/frontend/src/types/SubmisionStatus.ts b/frontend/src/types/SubmisionStatus.ts index 597bbece..159b1129 100644 --- a/frontend/src/types/SubmisionStatus.ts +++ b/frontend/src/types/SubmisionStatus.ts @@ -2,8 +2,7 @@ export class SubmissionStatus { constructor( public non_empty_groups: number, public groups_submitted: number, - public structure_checks_passed: number, - public extra_checks_passed: number, + public submissions_passed: number, ) {} /** @@ -15,8 +14,7 @@ export class SubmissionStatus { return new SubmissionStatus( submissionStatus.non_empty_groups, submissionStatus.groups_submitted, - submissionStatus.structure_checks_passed, - submissionStatus.extra_checks_passed, + submissionStatus.submissions_passed, ); } } From 968bf1b38c1111573f36491dfd100a330a408be0 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 14 May 2024 11:55:32 +0200 Subject: [PATCH 3/5] fix: cypress tests --- backend/ypovoli/settings.py | 4 ++-- frontend/cypress.config.js | 2 +- test.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/ypovoli/settings.py b/backend/ypovoli/settings.py index ca8d8152..259f96d6 100644 --- a/backend/ypovoli/settings.py +++ b/backend/ypovoli/settings.py @@ -46,8 +46,8 @@ # SECURITY WARNING: don't run with debug turned on in production! DEBUG = environ.get("DJANGO_DEBUG", "False").lower() in ["true", "1", "t"] DOMAIN_NAME = environ.get("DJANGO_DOMAIN_NAME", "localhost") -ALLOWED_HOSTS = [DOMAIN_NAME] -CSRF_TRUSTED_ORIGINS = ["https://" + DOMAIN_NAME] +ALLOWED_HOSTS = [DOMAIN_NAME, "nginx"] +CSRF_TRUSTED_ORIGINS = ["https://" + DOMAIN_NAME, "https://nginx"] # Application definition INSTALLED_APPS = [ diff --git a/frontend/cypress.config.js b/frontend/cypress.config.js index 6b759d73..83abcaba 100644 --- a/frontend/cypress.config.js +++ b/frontend/cypress.config.js @@ -2,7 +2,7 @@ import { defineConfig } from 'cypress'; export default defineConfig({ e2e: { - baseUrl: 'http://nginx', + baseUrl: 'https://nginx', specPattern: 'src/test/e2e/**/*.cy.{js,jsx,ts,tsx}', }, }); diff --git a/test.sh b/test.sh index 91cb140d..500e6073 100755 --- a/test.sh +++ b/test.sh @@ -92,7 +92,7 @@ fi exit_code=0 echo "-----------------" -if [ $vitest_exit -ne 0 ] || [ $django_exit -ne 0 ]; then +if [ $cypress_exit -ne 0 ] || [ $vitest_exit -ne 0 ] || [ $django_exit -ne 0 ]; then echo "Tests failed:" if [ $cypress_exit -ne 0 ]; then echo " - Cypress" From 4fc0caed31e3742b74471c12337491250f94952d Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 14 May 2024 12:32:05 +0200 Subject: [PATCH 4/5] chore: reduce docker image size --- frontend/Dockerfile.cypress | 8 ++++++++ test.yml | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 frontend/Dockerfile.cypress diff --git a/frontend/Dockerfile.cypress b/frontend/Dockerfile.cypress new file mode 100644 index 00000000..951a6f98 --- /dev/null +++ b/frontend/Dockerfile.cypress @@ -0,0 +1,8 @@ +ARG CHROME_VERSION="114.0.5735.133-1" +ARG NODE_VERSION="18.17.1" + +FROM cypress/factory + +WORKDIR /e2e + +RUN npm install --save-dev cypress diff --git a/test.yml b/test.yml index 0f274abd..439fb502 100644 --- a/test.yml +++ b/test.yml @@ -52,6 +52,8 @@ services: command: sh -c "./setup.sh && python manage.py runsslserver 0.0.0.0:8000" volumes: - $BACKEND_DIR:/code + depends_on: + - redis celery: <<: *common-keys-selab_test @@ -89,7 +91,12 @@ services: cypress: <<: *common-keys-selab_test container_name: cypress - image: cypress/included:cypress-12.17.3-node-18.16.0-chrome-114.0.5735.133-1-ff-114.0.2-edge-114.0.1823.51-1 + build: + context: $FRONTEND_DIR + dockerfile: Dockerfile.cypress + command: "npm run cypress:test" working_dir: /e2e volumes: - ${FRONTEND_DIR}:/e2e + depends_on: + - frontend From 0d316df163842cda221cc1f22a43b21ce9b3b213 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 14 May 2024 13:07:12 +0200 Subject: [PATCH 5/5] chore: npm install cypress --- frontend/Dockerfile.cypress | 4 ---- test.yml | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/Dockerfile.cypress b/frontend/Dockerfile.cypress index 951a6f98..509d7ca1 100644 --- a/frontend/Dockerfile.cypress +++ b/frontend/Dockerfile.cypress @@ -2,7 +2,3 @@ ARG CHROME_VERSION="114.0.5735.133-1" ARG NODE_VERSION="18.17.1" FROM cypress/factory - -WORKDIR /e2e - -RUN npm install --save-dev cypress diff --git a/test.yml b/test.yml index 439fb502..05051584 100644 --- a/test.yml +++ b/test.yml @@ -94,9 +94,10 @@ services: build: context: $FRONTEND_DIR dockerfile: Dockerfile.cypress - command: "npm run cypress:test" + command: sh -c "npm install && npm run cypress:test" working_dir: /e2e volumes: - ${FRONTEND_DIR}:/e2e + - /e2e/node_modules depends_on: - frontend