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