Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • j.michal/grady
1 result
Show changes
Commits on Source (5)
Showing
with 370 additions and 219 deletions
# Generated by Django 2.1.5 on 2019-03-06 16:11
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('core', '0011_auto_20181001_1259'),
]
operations = [
migrations.AlterField(
model_name='studentinfo',
name='exam',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='students', to='core.ExamType'),
),
]
...@@ -280,9 +280,9 @@ class StudentInfo(models.Model): ...@@ -280,9 +280,9 @@ class StudentInfo(models.Model):
max_length=30, max_length=30,
default=random_matrikel_no) default=random_matrikel_no)
exam = models.ForeignKey('ExamType', exam = models.ForeignKey('ExamType',
on_delete=models.SET_NULL, on_delete=models.CASCADE,
related_name='students', related_name='students',
null=True) null=False)
user = models.OneToOneField(get_user_model(), user = models.OneToOneField(get_user_model(),
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='student') related_name='student')
......
...@@ -5,7 +5,7 @@ from rest_framework.test import (APIRequestFactory, APITestCase, ...@@ -5,7 +5,7 @@ from rest_framework.test import (APIRequestFactory, APITestCase,
from core.views import (ExamApiViewSet, StudentReviewerApiViewSet, from core.views import (ExamApiViewSet, StudentReviewerApiViewSet,
StudentSelfApiView, TutorApiViewSet) StudentSelfApiView, TutorApiViewSet)
from util.factories import GradyUserFactory from util.factories import GradyUserFactory, make_exams
class AccessRightsOfStudentAPIViewTests(APITestCase): class AccessRightsOfStudentAPIViewTests(APITestCase):
...@@ -18,7 +18,12 @@ class AccessRightsOfStudentAPIViewTests(APITestCase): ...@@ -18,7 +18,12 @@ class AccessRightsOfStudentAPIViewTests(APITestCase):
cls.user_factory = GradyUserFactory() cls.user_factory = GradyUserFactory()
def setUp(self): def setUp(self):
self.student = self.user_factory.make_student() self.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
self.student = self.user_factory.make_student(exam=self.exam)
self.tutor = self.user_factory.make_tutor() self.tutor = self.user_factory.make_tutor()
self.reviewer = self.user_factory.make_reviewer() self.reviewer = self.user_factory.make_reviewer()
self.request = self.factory.get(reverse('student-page')) self.request = self.factory.get(reverse('student-page'))
...@@ -53,7 +58,12 @@ class AccessRightsOfTutorAPIViewTests(APITestCase): ...@@ -53,7 +58,12 @@ class AccessRightsOfTutorAPIViewTests(APITestCase):
cls.user_factory = GradyUserFactory() cls.user_factory = GradyUserFactory()
def setUp(self): def setUp(self):
self.student = self.user_factory.make_student() self.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
self.student = self.user_factory.make_student(exam=self.exam)
self.tutor = self.user_factory.make_tutor() self.tutor = self.user_factory.make_tutor()
self.reviewer = self.user_factory.make_reviewer() self.reviewer = self.user_factory.make_reviewer()
self.request = self.factory.get(reverse('tutor-list')) self.request = self.factory.get(reverse('tutor-list'))
...@@ -89,7 +99,12 @@ class AccessRightsOfStudentReviewerAPIViewTest(APITestCase): ...@@ -89,7 +99,12 @@ class AccessRightsOfStudentReviewerAPIViewTest(APITestCase):
cls.user_factory = GradyUserFactory() cls.user_factory = GradyUserFactory()
def setUp(self): def setUp(self):
self.student = self.user_factory.make_student() self.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
self.student = self.user_factory.make_student(exam=self.exam)
self.tutor = self.user_factory.make_tutor() self.tutor = self.user_factory.make_tutor()
self.reviewer = self.user_factory.make_reviewer() self.reviewer = self.user_factory.make_reviewer()
self.request = self.factory.get(reverse('student-list')) self.request = self.factory.get(reverse('student-list'))
...@@ -127,7 +142,12 @@ class AccessRightsOfExamTypeAPIViewTest(APITestCase): ...@@ -127,7 +142,12 @@ class AccessRightsOfExamTypeAPIViewTest(APITestCase):
cls.user_factory = GradyUserFactory() cls.user_factory = GradyUserFactory()
def setUp(self): def setUp(self):
self.student = self.user_factory.make_student() self.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
self.student = self.user_factory.make_student(exam=self.exam)
self.tutor = self.user_factory.make_tutor() self.tutor = self.user_factory.make_tutor()
self.reviewer = self.user_factory.make_reviewer() self.reviewer = self.user_factory.make_reviewer()
self.request = self.factory.get(reverse('examtype-list')) self.request = self.factory.get(reverse('examtype-list'))
......
...@@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model ...@@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model
from django.core.management import call_command from django.core.management import call_command
from django.test import TestCase from django.test import TestCase
from util.factories import GradyUserFactory from util.factories import GradyUserFactory, make_exams
class CommandsTestCase(TestCase): class CommandsTestCase(TestCase):
...@@ -22,7 +22,12 @@ class CommandsTestCase(TestCase): ...@@ -22,7 +22,12 @@ class CommandsTestCase(TestCase):
self.assertFalse(someone.is_active) self.assertFalse(someone.is_active)
def test_replaceusernames(self): def test_replaceusernames(self):
self.factory.make_student(identifier=88884444, username='before') self.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
self.factory.make_student(identifier=88884444, username='before', exam=self.exam)
with tempfile.NamedTemporaryFile() as matno2username: with tempfile.NamedTemporaryFile() as matno2username:
matno2username.write(json.dumps({'88884444': 'after'}).encode()) matno2username.write(json.dumps({'88884444': 'after'}).encode())
......
...@@ -2,7 +2,7 @@ from django.test import TestCase ...@@ -2,7 +2,7 @@ from django.test import TestCase
from core import models from core import models
from core.models import StudentInfo from core.models import StudentInfo
from util.factories import GradyUserFactory from util.factories import GradyUserFactory, make_exams
class FactoryTestCase(TestCase): class FactoryTestCase(TestCase):
...@@ -10,11 +10,15 @@ class FactoryTestCase(TestCase): ...@@ -10,11 +10,15 @@ class FactoryTestCase(TestCase):
factory = GradyUserFactory() factory = GradyUserFactory()
def test_make_student(self): def test_make_student(self):
self.exam = make_exams(exams=[{
user = self.factory.make_student() 'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
user = self.factory.make_student(exam=self.exam)
self.assertEqual(StudentInfo.objects.count(), 1) self.assertEqual(StudentInfo.objects.count(), 1)
self.assertEqual(user.student.exam, None) self.assertEqual(user.student.exam.module_reference, "Test Exam 01")
self.assertEqual(len(str(user.student.matrikel_no)), 8) self.assertEqual(len(str(user.student.matrikel_no)), 8)
def test_can_create_reviewer(self): def test_can_create_reviewer(self):
...@@ -30,9 +34,19 @@ class FactoryTestCase(TestCase): ...@@ -30,9 +34,19 @@ class FactoryTestCase(TestCase):
models.UserAccount.objects.all()) models.UserAccount.objects.all())
def test_can_create_student_user(self): def test_can_create_student_user(self):
self.assertIn(self.factory.make_student(), self.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
self.assertIn(self.factory.make_student(exam=self.exam),
models.UserAccount.objects.all()) models.UserAccount.objects.all())
def test_can_create_student_info(self): def test_can_create_student_info(self):
self.assertIn(self.factory.make_student().student, self.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
self.assertIn(self.factory.make_student(exam=self.exam).student,
StudentInfo.objects.all()) StudentInfo.objects.all())
...@@ -5,7 +5,7 @@ from rest_framework.test import APIRequestFactory, APITestCase ...@@ -5,7 +5,7 @@ from rest_framework.test import APIRequestFactory, APITestCase
from core import models from core import models
from core.models import Feedback, FeedbackComment, Submission, SubmissionType from core.models import Feedback, FeedbackComment, Submission, SubmissionType
from util.factories import GradyUserFactory, make_test_data from util.factories import GradyUserFactory, make_test_data, make_exams
class FeedbackRetrieveTestCase(APITestCase): class FeedbackRetrieveTestCase(APITestCase):
...@@ -17,7 +17,12 @@ class FeedbackRetrieveTestCase(APITestCase): ...@@ -17,7 +17,12 @@ class FeedbackRetrieveTestCase(APITestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.score = 23 cls.score = 23
cls.tutor = cls.factory.make_tutor() cls.tutor = cls.factory.make_tutor()
cls.student = cls.factory.make_student() cls.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
cls.student = cls.factory.make_student(exam=cls.exam)
cls.reviewer = cls.factory.make_reviewer() cls.reviewer = cls.factory.make_reviewer()
cls.tutors = [cls.tutor, cls.reviewer] cls.tutors = [cls.tutor, cls.reviewer]
cls.request_factory = APIRequestFactory() cls.request_factory = APIRequestFactory()
...@@ -91,7 +96,12 @@ class FeedbackCreateTestCase(APITestCase): ...@@ -91,7 +96,12 @@ class FeedbackCreateTestCase(APITestCase):
cls.url = '/api/feedback/' cls.url = '/api/feedback/'
cls.user_factory = GradyUserFactory() cls.user_factory = GradyUserFactory()
cls.tutor = cls.user_factory.make_tutor(password='p') cls.tutor = cls.user_factory.make_tutor(password='p')
cls.student = cls.user_factory.make_student() cls.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
cls.student = cls.user_factory.make_student(exam=cls.exam)
cls.submission_type = SubmissionType.objects.create( cls.submission_type = SubmissionType.objects.create(
name='Cooking some crystal with Jesse', name='Cooking some crystal with Jesse',
full_score=100 full_score=100
...@@ -282,6 +292,11 @@ class FeedbackPatchTestCase(APITestCase): ...@@ -282,6 +292,11 @@ class FeedbackPatchTestCase(APITestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.burl = '/api/feedback/' cls.burl = '/api/feedback/'
cls.data = make_test_data({ cls.data = make_test_data({
'exams': [{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}],
'submission_types': [ 'submission_types': [
{ {
'name': '01. Sort this or that', 'name': '01. Sort this or that',
...@@ -290,7 +305,10 @@ class FeedbackPatchTestCase(APITestCase): ...@@ -290,7 +305,10 @@ class FeedbackPatchTestCase(APITestCase):
'solution': 'Trivial!' 'solution': 'Trivial!'
}], }],
'students': [ 'students': [
{'username': 'student01'} {
'username': 'student01',
'exam': 'Test Exam 01'
}
], ],
'tutors': [ 'tutors': [
{'username': 'tutor01'}, {'username': 'tutor01'},
...@@ -395,6 +413,11 @@ class FeedbackCommentApiEndpointTest(APITestCase): ...@@ -395,6 +413,11 @@ class FeedbackCommentApiEndpointTest(APITestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.burl = '/api/feedback/' cls.burl = '/api/feedback/'
cls.data = make_test_data({ cls.data = make_test_data({
'exams': [{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}],
'submission_types': [ 'submission_types': [
{ {
'name': '01. Sort this or that', 'name': '01. Sort this or that',
...@@ -403,7 +426,10 @@ class FeedbackCommentApiEndpointTest(APITestCase): ...@@ -403,7 +426,10 @@ class FeedbackCommentApiEndpointTest(APITestCase):
'solution': 'Trivial!' 'solution': 'Trivial!'
}], }],
'students': [ 'students': [
{'username': 'student01'} {
'username': 'student01',
'exam': 'Test Exam 01'
}
], ],
'tutors': [ 'tutors': [
{'username': 'tutor01'}, {'username': 'tutor01'},
......
...@@ -3,7 +3,7 @@ from rest_framework.test import (APIRequestFactory, APITestCase, ...@@ -3,7 +3,7 @@ from rest_framework.test import (APIRequestFactory, APITestCase,
force_authenticate) force_authenticate)
from core.views import get_user_role from core.views import get_user_role
from util.factories import GradyUserFactory from util.factories import GradyUserFactory, make_exams
class GetUserRoleTest(APITestCase): class GetUserRoleTest(APITestCase):
...@@ -11,7 +11,12 @@ class GetUserRoleTest(APITestCase): ...@@ -11,7 +11,12 @@ class GetUserRoleTest(APITestCase):
def setUpTestData(cls): def setUpTestData(cls):
cls.factory = APIRequestFactory() cls.factory = APIRequestFactory()
cls.user_factory = GradyUserFactory() cls.user_factory = GradyUserFactory()
cls.student = cls.user_factory.make_student() cls.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
cls.student = cls.user_factory.make_student(exam=cls.exam)
cls.tutor = cls.user_factory.make_tutor() cls.tutor = cls.user_factory.make_tutor()
cls.reviewer = cls.user_factory.make_reviewer() cls.reviewer = cls.user_factory.make_reviewer()
......
...@@ -130,6 +130,11 @@ class StudentSelfSubmissionsTests(APITestCase): ...@@ -130,6 +130,11 @@ class StudentSelfSubmissionsTests(APITestCase):
def setUp(self): def setUp(self):
self.test_data = make_test_data(data_dict={ self.test_data = make_test_data(data_dict={
'exams': [{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}],
'submission_types': [{ 'submission_types': [{
'name': 'problem01', 'name': 'problem01',
'full_score': 10, 'full_score': 10,
...@@ -138,6 +143,7 @@ class StudentSelfSubmissionsTests(APITestCase): ...@@ -138,6 +143,7 @@ class StudentSelfSubmissionsTests(APITestCase):
}], }],
'students': [{ 'students': [{
'username': 'user01', 'username': 'user01',
'exam': 'Test Exam 01'
}], }],
'tutors': [ 'tutors': [
{ {
......
...@@ -4,7 +4,7 @@ from rest_framework.test import APIClient, APITestCase ...@@ -4,7 +4,7 @@ from rest_framework.test import APIClient, APITestCase
from core import models from core import models
from core.models import (Submission, SubmissionSubscription, SubmissionType, from core.models import (Submission, SubmissionSubscription, SubmissionType,
SubscriptionEnded, SubscriptionTemporarilyEnded, TutorSubmissionAssignment) SubscriptionEnded, SubscriptionTemporarilyEnded, TutorSubmissionAssignment)
from util.factories import GradyUserFactory, make_test_data from util.factories import GradyUserFactory, make_test_data, make_exams
class SubmissionSubscriptionRandomTest(APITestCase): class SubmissionSubscriptionRandomTest(APITestCase):
...@@ -15,8 +15,13 @@ class SubmissionSubscriptionRandomTest(APITestCase): ...@@ -15,8 +15,13 @@ class SubmissionSubscriptionRandomTest(APITestCase):
def setUp(self): def setUp(self):
self.t = self.user_factory.make_tutor() self.t = self.user_factory.make_tutor()
self.s1 = self.user_factory.make_student() self.exam = make_exams(exams=[{
self.s2 = self.user_factory.make_student() 'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
self.s1 = self.user_factory.make_student(exam=self.exam)
self.s2 = self.user_factory.make_student(exam=self.exam)
self.submission_type = SubmissionType.objects.create( self.submission_type = SubmissionType.objects.create(
name='submission_01', full_score=14) name='submission_01', full_score=14)
...@@ -97,6 +102,11 @@ class TestApiEndpoints(APITestCase): ...@@ -97,6 +102,11 @@ class TestApiEndpoints(APITestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.data = make_test_data(data_dict={ cls.data = make_test_data(data_dict={
'exams': [{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}],
'submission_types': [ 'submission_types': [
{ {
'name': '01. Sort this or that', 'name': '01. Sort this or that',
...@@ -112,8 +122,14 @@ class TestApiEndpoints(APITestCase): ...@@ -112,8 +122,14 @@ class TestApiEndpoints(APITestCase):
} }
], ],
'students': [ 'students': [
{'username': 'student01'}, {
{'username': 'student02'} 'username': 'student01',
'exam': 'Test Exam 01'
},
{
'username': 'student02',
'exam': 'Test Exam 01'
}
], ],
'tutors': [ 'tutors': [
{'username': 'tutor01'}, {'username': 'tutor01'},
......
...@@ -56,14 +56,25 @@ class TutorListTests(APITestCase): ...@@ -56,14 +56,25 @@ class TutorListTests(APITestCase):
view = TutorApiViewSet.as_view({'get': 'list'}) view = TutorApiViewSet.as_view({'get': 'list'})
data = make_test_data(data_dict={ data = make_test_data(data_dict={
'exams': [{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}],
'submission_types': [{ 'submission_types': [{
'name': '01. Sort this or that', 'name': '01. Sort this or that',
'full_score': 35, 'full_score': 35,
'description': 'Very complicated', 'description': 'Very complicated',
'solution': 'Trivial!'}], 'solution': 'Trivial!'}],
'students': [ 'students': [
{'username': 'student01'}, {
{'username': 'student02'} 'username': 'student01',
'exam': 'Test Exam 01'
},
{
'username': 'student02',
'exam': 'Test Exam 01'
}
], ],
'tutors': [ 'tutors': [
{'username': 'tutor01'}, {'username': 'tutor01'},
......
from rest_framework import status from rest_framework import status
from rest_framework.test import (APIClient, APITestCase) from rest_framework.test import (APIClient, APITestCase)
from util.factories import GradyUserFactory from util.factories import GradyUserFactory, make_exams
class TutorReviewerCanChangePasswordTests(APITestCase): class TutorReviewerCanChangePasswordTests(APITestCase):
...@@ -75,7 +75,12 @@ class TutorReviewerCanChangePasswordTests(APITestCase): ...@@ -75,7 +75,12 @@ class TutorReviewerCanChangePasswordTests(APITestCase):
self.assertTrue(ret) self.assertTrue(ret)
def test_student_cant_change_password(self): def test_student_cant_change_password(self):
student = self.user_factory.make_student(password='l') self.exam = make_exams(exams=[{
'module_reference': 'Test Exam 01',
'total_score': 100,
'pass_score': 60,
}])[0]
student = self.user_factory.make_student(password='l', exam=self.exam)
res = self._change_password(student) res = self._change_password(student)
self.assertEqual(status.HTTP_403_FORBIDDEN, res.status_code) self.assertEqual(status.HTTP_403_FORBIDDEN, res.status_code)
ret = self.client.login(username=student.username, ret = self.client.login(username=student.username,
......
import Component from 'vue-class-component'
// Register the router hooks with their names
Component.registerHooks([
'beforeRouteEnter',
'beforeRouteLeave',
'beforeRouteUpdate' // for vue-router 2.2+
])
<template> <template>
<v-card class="mx-auto center-page" id="subscription-ended"> <v-card class="mx-auto center-page" id="subscription-ended">
<v-card-title class="title"> <v-card-title class="title">
It seems like your subscription has (temporarily) ended. No submissions left
</v-card-title> </v-card-title>
<v-card-text> <v-card-text>
If you've been validating feedback or resolving conflicts those subscriptions might become active again.<br/> All submissions for <b> {{ submissionTypeName() }} </b> in the current stage have been corrected. If you've
If that happens they'll become clickable in the sidebar. been validating feedback or <br/>
resolving conflicts some submissions may become active again.
If that is the case they will appear clickable in the sidebar again.
</v-card-text> </v-card-text>
<v-card-actions class="text-xs-center"> <v-card-actions class="text-xs-center">
<v-btn to="/home"> <v-btn to="/home">
...@@ -18,10 +20,21 @@ ...@@ -18,10 +20,21 @@
</v-card> </v-card>
</template> </template>
<script> <script lang="ts">
export default {
name: 'subscription-ended' import Vue from 'vue'
import Component from 'vue-class-component'
import store from '@/store/store'
@Component
export default class SubscriptionEnded extends Vue {
get submissionTypes () { return store.state.submissionTypes }
submissionTypeName () {
return this.submissionTypes[this.$route.params.typePk].name
}
} }
</script> </script>
<style scoped> <style scoped>
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
Tasks Tasks
</v-toolbar-title> </v-toolbar-title>
<v-spacer/> <v-spacer/>
<v-btn icon @click="getSubscriptions"> <v-btn icon @click="getSubscriptions(false)">
<v-icon v-if="!updating">refresh</v-icon> <v-icon v-if="!updating">refresh</v-icon>
<v-progress-circular <v-progress-circular
v-else v-else
...@@ -27,54 +27,65 @@ ...@@ -27,54 +27,65 @@
</v-card> </v-card>
</template> </template>
<script> <script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import { Prop } from 'vue-property-decorator'
import { mapGetters, mapActions, mapState } from 'vuex' import { mapGetters, mapActions, mapState } from 'vuex'
import { UI } from '@/store/modules/ui' import { UI } from '@/store/modules/ui'
import { actions } from '@/store/actions' import { actions } from '@/store/actions'
import SubscriptionCreation from '@/components/subscriptions/SubscriptionCreation' import SubscriptionCreation from '@/components/subscriptions/SubscriptionCreation.vue'
import SubscriptionForList from '@/components/subscriptions/SubscriptionForList' import SubscriptionForList from '@/components/subscriptions/SubscriptionForList.vue'
import SubscriptionsForStage from '@/components/subscriptions/SubscriptionsForStage' import SubscriptionsForStage from '@/components/subscriptions/SubscriptionsForStage.vue'
import { Subscriptions } from '@/store/modules/subscriptions' import { Subscriptions } from '@/store/modules/subscriptions'
export default { @Component({
name: 'subscription-list',
components: { components: {
SubscriptionsForStage, SubscriptionsForStage,
SubscriptionForList, SubscriptionForList,
SubscriptionCreation }, SubscriptionCreation
name: 'subscription-list',
props: {
sidebar: {
type: Boolean,
default: false
}
},
data () {
return {
selectedStage: null,
updating: false
}
}, },
computed: { })
subscriptions () { return Subscriptions.state.subscriptions }, export default class SubscriptionList extends Vue {
stages () { return Subscriptions.availableStages }, @Prop({type: Boolean, default: false}) sidebar!: boolean
stagesReadable () { return Subscriptions.availableStagesReadable },
showDetail () { selectedStage = null
return !this.sidebar || (this.sidebar && !UI.state.sideBarCollapsed) updating = false
} timer = 0
},
methods: { get subscriptions () { return Subscriptions.state.subscriptions }
async getSubscriptions () { get stages () { return Subscriptions.availableStages }
get stagesReadable () { return Subscriptions.availableStagesReadable }
get showDetail () {
return !this.sidebar || (this.sidebar && !UI.state.sideBarCollapsed)
}
async getSubscriptions (silent: boolean) {
if (silent === false) {
this.updating = true this.updating = true
const subscriptions = await Subscriptions.getSubscriptions()
this.updating = false
return subscriptions
} }
}, const subscriptions = await Subscriptions.getSubscriptions()
created () { this.updating = false
const typesAndSubscriptions = [actions.updateSubmissionTypes(), Subscriptions.getSubscriptions()] return subscriptions
Promise.all(typesAndSubscriptions).then(() => { }
mounted() {
this.timer = setInterval(() => {
this.getSubscriptions(true)
}, 30 * 1e3)
}
beforeDestroy() {
clearInterval(this.timer)
}
created() {
const submissionTypes = actions.updateSubmissionTypes()
const subscriptions = Subscriptions.getSubscriptions()
Promise.all([submissionTypes, subscriptions]).then(() => {
Subscriptions.subscribeToAll() Subscriptions.subscribeToAll()
Subscriptions.cleanAssignmentsFromSubscriptions() Subscriptions.cleanAssignmentsFromSubscriptions(true)
}) })
} }
} }
......
import './class-component-hooks'
import Vue from 'vue' import Vue from 'vue'
import store from './store/store' import store from './store/store'
import App from './App.vue' import App from './App.vue'
...@@ -6,6 +9,7 @@ import Vuetify from 'vuetify' ...@@ -6,6 +9,7 @@ import Vuetify from 'vuetify'
import Notifications from 'vue-notification' import Notifications from 'vue-notification'
import Clipboard from 'v-clipboard' import Clipboard from 'v-clipboard'
import 'vuetify/dist/vuetify.min.css' import 'vuetify/dist/vuetify.min.css'
import 'highlight.js/styles/atom-one-light.css' import 'highlight.js/styles/atom-one-light.css'
...@@ -22,4 +26,4 @@ export default new Vue({ ...@@ -22,4 +26,4 @@ export default new Vue({
router: router, router: router,
store, store,
render: h => h(App) render: h => h(App)
}).$mount(el) }).$mount(el)
\ No newline at end of file
...@@ -15,7 +15,7 @@ export interface Assignment { ...@@ -15,7 +15,7 @@ export interface Assignment {
* @type {string} * @type {string}
* @memberof Assignment * @memberof Assignment
*/ */
submission?: string submission?: string | SubmissionAssignment
/** /**
* *
* @type {boolean} * @type {boolean}
...@@ -40,6 +40,13 @@ export interface Assignment { ...@@ -40,6 +40,13 @@ export interface Assignment {
subscription?: string subscription?: string
} }
export interface SubmissionAssignment {
text: string,
type: string
full_score: number,
tests: Test[]
}
/** /**
* *
* @export * @export
......
...@@ -31,87 +31,85 @@ ...@@ -31,87 +31,85 @@
</v-layout> </v-layout>
</template> </template>
<script>
import SubmissionCorrection from '@/components/submission_notes/SubmissionCorrection' <script lang="ts">
import SubmissionType from '@/components/SubmissionType' import { Vue, Component} from 'vue-property-decorator'
import { Route, NavigationGuard } from 'vue-router'
import SubmissionCorrection from '@/components/submission_notes/SubmissionCorrection.vue'
import SubmissionType from '@/components/SubmissionType.vue'
import store from '@/store/store' import store from '@/store/store'
import SubmissionTests from '@/components/SubmissionTests' import { SubmissionNotes } from '@/store/modules/submission-notes'
import SubmissionTests from '@/components/SubmissionTests.vue'
import { Subscriptions } from '@/store/modules/subscriptions' import { Subscriptions } from '@/store/modules/subscriptions'
import RouteChangeConfirmation from '@/components/submission_notes/RouteChangeConfirmation' import RouteChangeConfirmation from '@/components/submission_notes/RouteChangeConfirmation.vue'
import { getters } from '@/store/getters' import { getters } from '@/store/getters'
import { SubmissionAssignment } from '@/models'
function onRouteEnterOrUpdate (to, from, next) { const onRouteEnterOrUpdate: NavigationGuard = function (to, from, next) {
Subscriptions.changeActiveSubscription(to.params['pk']).then(() => { Subscriptions.changeToSubscription(to.params['pk']).then(() => {
next() next()
}) })
} }
export default { @Component({
components: { components: {
RouteChangeConfirmation, RouteChangeConfirmation,
SubmissionTests, SubmissionTests,
SubmissionType, SubmissionType,
SubmissionCorrection SubmissionCorrection
}, }
name: 'subscription-work-page', })
data () { export default class SubscriptionWorkPage extends Vue {
return {
subscriptionActive: true, subscriptionActive = false
nextRoute: null nextRoute = () => {}
}
}, get subscription () {
computed: { return Subscriptions.state.subscriptions[this.$route.params['pk']]
subscription () { }
return Subscriptions.state.subscriptions[this.$route.params['pk']]
}, get currentAssignment () {
currentAssignment () { return Subscriptions.state.currentAssignment
return Subscriptions.state.assignmentQueue[0] }
},
submission () { get submission () {
return this.currentAssignment.submission return this.currentAssignment && this.currentAssignment.submission
}, }
submissionType () {
return getters.state.submissionTypes[this.submission.type] get submissionType () {
if (this.submission && (<SubmissionAssignment> this.submission).type) {
return getters.state.submissionTypes[(<SubmissionAssignment>this.submission).type]
} }
}, }
beforeRouteEnter (to, from, next) {
beforeRouteEnter (to: Route, from: Route, next: (to?: any) => void ) {
onRouteEnterOrUpdate(to, from, next) onRouteEnterOrUpdate(to, from, next)
}, }
beforeRouteUpdate (to, from, next) {
beforeRouteUpdate (this: SubscriptionWorkPage, to: Route, from: Route, next: (to?: any) => void) {
this.nextRoute = () => { this.nextRoute = () => {
onRouteEnterOrUpdate(to, from, next) onRouteEnterOrUpdate(to, from, next)
} }
}, }
beforeRouteLeave (to, from, next) {
beforeRouteLeave (this: SubscriptionWorkPage, to: Route, from: Route, next: (to?: any) => void) {
if (to.name === 'subscription-ended') { if (to.name === 'subscription-ended') {
next() next()
} else { } else {
this.nextRoute = () => { this.nextRoute = () => {
Subscriptions.removeActiveSubscription()
next() next()
Subscriptions.deleteCurrentAssignment()
} }
} }
}, }
methods: {
startWorkOnNextAssignment () { startWorkOnNextAssignment () {
Subscriptions.getAssignmentsForActiveSubscription(1).then(([promise]) => { Subscriptions.createNextAssignment().catch(() => {
promise.then(assignment => { const typePk = SubmissionNotes.state.submission.type
Subscriptions.ADD_ASSIGNMENT_TO_QUEUE(assignment) this.$router.replace(typePk + '/ended')
}).finally(() => { Subscriptions.SET_CURRENT_ASSIGNMENT(undefined)
Subscriptions.POP_ASSIGNMENT_FROM_QUEUE()
})
})
}
},
watch: {
currentAssignment (val) {
this.$vuetify.goTo(0, { duration: 200, easing: 'easeInOutCubic' })
if (val === undefined) {
this.$router.replace('ended')
Subscriptions.removeActiveSubscription()
Subscriptions.getSubscriptions() Subscriptions.getSubscriptions()
} })
}
} }
} }
</script> </script>
......
...@@ -80,17 +80,17 @@ const router = new Router({ ...@@ -80,17 +80,17 @@ const router = new Router({
name: 'home', name: 'home',
component: StartPageSelector component: StartPageSelector
}, },
{
path: 'subscription/ended',
name: 'subscription-ended',
component: SubscriptionEnded
},
{ {
path: 'subscription/:pk', path: 'subscription/:pk',
name: 'subscription', name: 'subscription',
beforeEnter: tutorOrReviewerOnly, beforeEnter: tutorOrReviewerOnly,
component: SubscriptionWorkPage component: SubscriptionWorkPage
}, },
{
path: 'subscription/:typePk/ended',
name: 'subscription-ended',
component: SubscriptionEnded
},
{ {
path: 'feedback', path: 'feedback',
beforeEnter: tutorOrReviewerOnly, beforeEnter: tutorOrReviewerOnly,
......
...@@ -38,7 +38,7 @@ function ADD_ASSIGNMENTS_INFO (state: FeedbackTableState, assignments: Array<Ass ...@@ -38,7 +38,7 @@ function ADD_ASSIGNMENTS_INFO (state: FeedbackTableState, assignments: Array<Ass
if (!assignment.submission || !assignment.stage) { if (!assignment.submission || !assignment.stage) {
throw Error() throw Error()
} }
const feedback = state.feedbackHist[assignment.submission] const feedback = state.feedbackHist[<string> assignment.submission]
feedback.history = { feedback.history = {
...feedback.history, ...feedback.history,
[assignment.stage]: { [assignment.stage]: {
......
...@@ -9,24 +9,20 @@ import { getStoreBuilder, BareActionContext } from 'vuex-typex' ...@@ -9,24 +9,20 @@ import { getStoreBuilder, BareActionContext } from 'vuex-typex'
export interface SubscriptionsState { export interface SubscriptionsState {
subscriptions: {[pk: string]: Subscription} subscriptions: {[pk: string]: Subscription}
assignmentQueue: Array<Assignment> currentAssignment?: Assignment
activeSubscriptionPk: string
loading: boolean loading: boolean
} }
function initialState (): SubscriptionsState { function initialState (): SubscriptionsState {
return { return {
subscriptions: {}, subscriptions: {},
assignmentQueue: [], currentAssignment: undefined,
activeSubscriptionPk: '',
loading: false loading: false
} }
} }
const mb = getStoreBuilder<RootState>().module('Subscriptions', initialState()) const mb = getStoreBuilder<RootState>().module('Subscriptions', initialState())
const MAX_NUMBER_OF_ASSIGNMENTS = 2
const stateGetter = mb.state() const stateGetter = mb.state()
const availableTypesGetter = mb.read(function availableTypes (state, getters) { const availableTypesGetter = mb.read(function availableTypes (state, getters) {
...@@ -36,6 +32,7 @@ const availableTypesGetter = mb.read(function availableTypes (state, getters) { ...@@ -36,6 +32,7 @@ const availableTypesGetter = mb.read(function availableTypes (state, getters) {
} }
return types return types
}) })
const availableStagesGetter = mb.read(function availableStages (state, getters) { const availableStagesGetter = mb.read(function availableStages (state, getters) {
let stages = [Subscription.FeedbackStageEnum.Creation, Subscription.FeedbackStageEnum.Validation] let stages = [Subscription.FeedbackStageEnum.Creation, Subscription.FeedbackStageEnum.Validation]
if (Authentication.isReviewer) { if (Authentication.isReviewer) {
...@@ -43,6 +40,7 @@ const availableStagesGetter = mb.read(function availableStages (state, getters) ...@@ -43,6 +40,7 @@ const availableStagesGetter = mb.read(function availableStages (state, getters)
} }
return stages return stages
}) })
const availableStagesReadableGetter = mb.read(function availableStagesReadable (state, getters) { const availableStagesReadableGetter = mb.read(function availableStagesReadable (state, getters) {
let stages = ['initial', 'validate'] let stages = ['initial', 'validate']
if (Authentication.isReviewer) { if (Authentication.isReviewer) {
...@@ -50,15 +48,23 @@ const availableStagesReadableGetter = mb.read(function availableStagesReadable ( ...@@ -50,15 +48,23 @@ const availableStagesReadableGetter = mb.read(function availableStagesReadable (
} }
return stages return stages
}) })
const availableSubmissionTypeQueryKeysGetter = mb.read(function availableSubmissionTypeQueryKeys (state, getters, rootState) { const availableSubmissionTypeQueryKeysGetter = mb.read(function availableSubmissionTypeQueryKeys (state, getters, rootState) {
return Object.values(rootState.submissionTypes).map((subType: any) => subType.pk) return Object.values(rootState.submissionTypes).map((subType: any) => subType.pk)
}) })
const availableExamTypeQueryKeysGetter = mb.read(function availableExamTypeQueryKeys (state, getters, rootState) { const availableExamTypeQueryKeysGetter = mb.read(function availableExamTypeQueryKeys (state, getters, rootState) {
return Object.values(rootState.examTypes).map((examType: any) => examType.pk) return Object.values(rootState.examTypes).map((examType: any) => examType.pk)
}) })
const activeSubscriptionGetter = mb.read(function activeSubscription (state) { const activeSubscriptionGetter = mb.read(function activeSubscription (state) {
return state.subscriptions[state.activeSubscriptionPk] if (state.currentAssignment && state.currentAssignment.subscription) {
return state.subscriptions[state.currentAssignment.subscription]
}
return undefined
}) })
const resolveSubscriptionKeyToNameGetter = mb.read(function resolveSubscriptionKeyToName (state, getters, rootState) { const resolveSubscriptionKeyToNameGetter = mb.read(function resolveSubscriptionKeyToName (state, getters, rootState) {
return (subscription: {queryType: Subscription.QueryTypeEnum, queryKey: string}) => { return (subscription: {queryType: Subscription.QueryTypeEnum, queryKey: string}) => {
switch (subscription.queryType) { switch (subscription.queryType) {
...@@ -127,21 +133,15 @@ function SET_SUBSCRIPTIONS (state: SubscriptionsState, subscriptions: Array<Subs ...@@ -127,21 +133,15 @@ function SET_SUBSCRIPTIONS (state: SubscriptionsState, subscriptions: Array<Subs
return acc return acc
}, {}) }, {})
} }
function SET_SUBSCRIPTION (state: SubscriptionsState, subscription: Subscription): void { function SET_SUBSCRIPTION (state: SubscriptionsState, subscription: Subscription): void {
Vue.set(state.subscriptions, subscription.pk, subscription) Vue.set(state.subscriptions, subscription.pk, subscription)
} }
function SET_ACTIVE_SUBSCRIPTION_PK (state: SubscriptionsState, subscriptionPk: string): void {
state.activeSubscriptionPk = subscriptionPk function SET_CURRENT_ASSIGNMENT (state: SubscriptionsState, assignment?: Assignment): void {
} state.currentAssignment = assignment
function SET_ASSIGNMENT_QUEUE (state: SubscriptionsState, queue: Array<Assignment>): void {
state.assignmentQueue = queue
}
function ADD_ASSIGNMENT_TO_QUEUE (state: SubscriptionsState, assignment: Assignment): void {
state.assignmentQueue.push(assignment)
}
function POP_ASSIGNMENT_FROM_QUEUE (state: SubscriptionsState): void {
state.assignmentQueue.shift()
} }
function RESET_STATE (state: SubscriptionsState): void { function RESET_STATE (state: SubscriptionsState): void {
Object.assign(state, initialState()) Object.assign(state, initialState())
subscribeToAll.reset() subscribeToAll.reset()
...@@ -162,34 +162,43 @@ async function subscribeTo ( ...@@ -162,34 +162,43 @@ async function subscribeTo (
Subscriptions.SET_SUBSCRIPTION(subscription) Subscriptions.SET_SUBSCRIPTION(subscription)
return subscription return subscription
} }
async function getSubscriptions () { async function getSubscriptions () {
const subscriptions = await api.fetchSubscriptions() const subscriptions = await api.fetchSubscriptions()
Subscriptions.SET_SUBSCRIPTIONS(subscriptions) Subscriptions.SET_SUBSCRIPTIONS(subscriptions)
return subscriptions return subscriptions
} }
/**
* Creates as many assignments as needed to reach MAX_NUMBER_OF_ASSIGNMENTS
* @param numOfAssignments Use to override default behaviour of async function changeToSubscription({state}: BareActionContext<SubscriptionsState, RootState>, subscriptionPk: string) {
* creating MAX_NUMBER_OF_ASSIGNMENTS - assignmentQueue.length assignments const currAssignment = state.currentAssignment
*/ if (currAssignment && currAssignment.subscription == subscriptionPk) {
async function getAssignmentsForActiveSubscription return
(context: BareActionContext<SubscriptionsState, RootState>, numOfAssignments: number):
Promise<Promise<Assignment>[]> {
numOfAssignments = numOfAssignments || MAX_NUMBER_OF_ASSIGNMENTS - context.state.assignmentQueue.length
let assignmentsPromises = []
for (let i = 0; i < numOfAssignments; i++) {
assignmentsPromises.push(api.createAssignment({ subscription: Subscriptions.activeSubscription }))
} }
return assignmentsPromises
if (currAssignment) {
await api.deleteAssignment({assignment: currAssignment})
}
const newAssignment = await api.createAssignment({subscriptionPk})
Subscriptions.SET_CURRENT_ASSIGNMENT(newAssignment)
} }
async function deleteAssignment
(context: BareActionContext<SubscriptionsState, RootState>, assignment: Assignment) { async function createNextAssignment() {
return api.deleteAssignment({ assignment }) const activeSubscription = Subscriptions.activeSubscription
if (!activeSubscription) {
throw new Error("There must be an active Subscription before calling createNextAssignment")
}
const newAssignment = await api.createAssignment({subscription: activeSubscription})
Subscriptions.SET_CURRENT_ASSIGNMENT(newAssignment)
} }
async function cleanAssignmentsFromSubscriptions async function cleanAssignmentsFromSubscriptions
({ state }: BareActionContext<SubscriptionsState, RootState>, excludeActive = true) { ({ state }: BareActionContext<SubscriptionsState, RootState>, excludeActive = true) {
Object.values(state.subscriptions).forEach(subscription => { Object.values(state.subscriptions).forEach(subscription => {
if (!excludeActive || subscription.pk !== state.activeSubscriptionPk) { if (!excludeActive ||
!Subscriptions.activeSubscription ||
subscription.pk !== Subscriptions.activeSubscription.pk) {
if (subscription.assignments) { if (subscription.assignments) {
subscription.assignments.forEach(assignment => { subscription.assignments.forEach(assignment => {
api.deleteAssignment({ assignment }) api.deleteAssignment({ assignment })
...@@ -198,48 +207,26 @@ async function cleanAssignmentsFromSubscriptions ...@@ -198,48 +207,26 @@ async function cleanAssignmentsFromSubscriptions
} }
}) })
} }
async function skipAssignment ({ state }: BareActionContext<SubscriptionsState, RootState>) { async function skipAssignment ({ state }: BareActionContext<SubscriptionsState, RootState>) {
Subscriptions.deleteAssignment(state.assignmentQueue[0]) if (!state.currentAssignment || !state.currentAssignment.subscription) {
.then(() => { throw new Error("skipAssignment can only be called with active assignment")
// pass numOfAssignments = 1 to create 1 new assignment although maybe two are already in the queue,
// this is needed because otherwise the current assignment in the comp. might be unknown for a period
// which will result get incorrectly interpreted as a an ended subscription
return Subscriptions.getAssignmentsForActiveSubscription(1)
}).then(([promise]) => {
promise.then((assignment: Assignment) => {
Subscriptions.ADD_ASSIGNMENT_TO_QUEUE(assignment)
Subscriptions.POP_ASSIGNMENT_FROM_QUEUE()
})
})
}
async function deleteActiveAssignments ({ state }: BareActionContext<SubscriptionsState, RootState>) {
Promise.all(state.assignmentQueue.map(assignment => {
Subscriptions.deleteAssignment(assignment)
}))
}
async function changeActiveSubscription ({ state }: BareActionContext<SubscriptionsState, RootState>, subscriptionPk = '') {
if (subscriptionPk === state.activeSubscriptionPk) {
return
}
await Subscriptions.deleteActiveAssignments()
Subscriptions.SET_ACTIVE_SUBSCRIPTION_PK(subscriptionPk)
let assignmentsPromises = await Subscriptions.getAssignmentsForActiveSubscription(MAX_NUMBER_OF_ASSIGNMENTS)
let createdAssignments = []
// TODO refactor this since it's very bad to await promises in for loops
for (let promise of assignmentsPromises) {
try {
createdAssignments.push(await promise)
} catch (_) {}
} }
Subscriptions.SET_ASSIGNMENT_QUEUE(createdAssignments)
const newAssignment = await api.createAssignment({subscriptionPk: state.currentAssignment.subscription})
await api.deleteAssignment({assignment: state.currentAssignment })
Subscriptions.SET_CURRENT_ASSIGNMENT(newAssignment)
} }
async function removeActiveSubscription () {
await Subscriptions.deleteActiveAssignments() async function deleteCurrentAssignment ({ state }: BareActionContext<SubscriptionsState, RootState>) {
Subscriptions.SET_ASSIGNMENT_QUEUE([]) if (!state.currentAssignment) {
Subscriptions.SET_ACTIVE_SUBSCRIPTION_PK('') throw new Error("No active assignment to delete")
}
await api.deleteAssignment({assignment: state.currentAssignment})
Subscriptions.SET_CURRENT_ASSIGNMENT(undefined)
} }
// TODO use enums here
async function subscribeToType async function subscribeToType
(context: BareActionContext<SubscriptionsState, RootState>, type: Subscription.QueryTypeEnum) { (context: BareActionContext<SubscriptionsState, RootState>, type: Subscription.QueryTypeEnum) {
switch (type) { switch (type) {
...@@ -268,6 +255,7 @@ async function subscribeToType ...@@ -268,6 +255,7 @@ async function subscribeToType
break break
} }
} }
const subscribeToAll = once(async () => { const subscribeToAll = once(async () => {
return Promise.all(flatten(Subscriptions.availableTypes.map((type) => { return Promise.all(flatten(Subscriptions.availableTypes.map((type) => {
return Subscriptions.subscribeToType(type) return Subscriptions.subscribeToType(type)
...@@ -287,21 +275,16 @@ export const Subscriptions = { ...@@ -287,21 +275,16 @@ export const Subscriptions = {
SET_SUBSCRIPTIONS: mb.commit(SET_SUBSCRIPTIONS), SET_SUBSCRIPTIONS: mb.commit(SET_SUBSCRIPTIONS),
SET_SUBSCRIPTION: mb.commit(SET_SUBSCRIPTION), SET_SUBSCRIPTION: mb.commit(SET_SUBSCRIPTION),
SET_ACTIVE_SUBSCRIPTION_PK: mb.commit(SET_ACTIVE_SUBSCRIPTION_PK), SET_CURRENT_ASSIGNMENT: mb.commit(SET_CURRENT_ASSIGNMENT),
SET_ASSIGNMENT_QUEUE: mb.commit(SET_ASSIGNMENT_QUEUE),
ADD_ASSIGNMENT_TO_QUEUE: mb.commit(ADD_ASSIGNMENT_TO_QUEUE),
POP_ASSIGNMENT_FROM_QUEUE: mb.commit(POP_ASSIGNMENT_FROM_QUEUE),
RESET_STATE: mb.commit(RESET_STATE), RESET_STATE: mb.commit(RESET_STATE),
subscribeTo: mb.dispatch(subscribeTo), subscribeTo: mb.dispatch(subscribeTo),
getSubscriptions: mb.dispatch(getSubscriptions), getSubscriptions: mb.dispatch(getSubscriptions),
getAssignmentsForActiveSubscription: mb.dispatch(getAssignmentsForActiveSubscription),
deleteAssignment: mb.dispatch(deleteAssignment),
cleanAssignmentsFromSubscriptions: mb.dispatch(cleanAssignmentsFromSubscriptions), cleanAssignmentsFromSubscriptions: mb.dispatch(cleanAssignmentsFromSubscriptions),
changeToSubscription: mb.dispatch(changeToSubscription),
createNextAssignment: mb.dispatch(createNextAssignment),
skipAssignment: mb.dispatch(skipAssignment), skipAssignment: mb.dispatch(skipAssignment),
deleteActiveAssignments: mb.dispatch(deleteActiveAssignments), deleteCurrentAssignment: mb.dispatch(deleteCurrentAssignment),
changeActiveSubscription: mb.dispatch(changeActiveSubscription),
removeActiveSubscription: mb.dispatch(removeActiveSubscription),
subscribeToType: mb.dispatch(subscribeToType), subscribeToType: mb.dispatch(subscribeToType),
subscribeToAll: mb.dispatch(subscribeToAll, 'subscribeToAll') subscribeToAll: mb.dispatch(subscribeToAll, 'subscribeToAll')
} }