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 (3)
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')
} }
...@@ -211,11 +211,20 @@ class UntestedParent: ...@@ -211,11 +211,20 @@ class UntestedParent:
def test_can_validate_submission(self): def test_can_validate_submission(self):
self._login() self._login()
self._go_to_subscription() self._go_to_subscription()
code = self._reconstruct_submission_code()
self.write_comments_on_lines([(0, 'A comment by me')]) def correct():
self.browser.find_element_by_id('score-zero').click() code = self._reconstruct_submission_code()
self.browser.find_element_by_id('submit-feedback').click() self.write_comments_on_lines([(0, 'A comment by me')])
self.browser.find_element_by_id('score-zero').click()
self.browser.find_element_by_id('submit-feedback').click()
return code
code = correct()
WebDriverWait(self.browser, 10).until(self.wait_until_code_changes(code)) WebDriverWait(self.browser, 10).until(self.wait_until_code_changes(code))
correct()
sub_url = 'subscription/' + str(self.sub_type.pk) + '/ended'
WebDriverWait(self.browser, 10).until(ec.url_contains(sub_url))
reset_browser_after_test(self.browser, self.live_server_url) # logs out user reset_browser_after_test(self.browser, self.live_server_url) # logs out user
user_snd = 'tutor_snd' user_snd = 'tutor_snd'
...@@ -225,13 +234,38 @@ class UntestedParent: ...@@ -225,13 +234,38 @@ class UntestedParent:
login(self.browser, self.live_server_url, user_snd, password) login(self.browser, self.live_server_url, user_snd, password)
self._go_to_subscription(stage='validate') self._go_to_subscription(stage='validate')
self.write_comments_on_lines([(0, 'I disagree'), (1, 'Full points!')]) self.write_comments_on_lines([(0, 'I disagree'), (1, 'Full points!')])
code_final = self._reconstruct_submission_code()
self.browser.find_element_by_id('score-full').click() self.browser.find_element_by_id('score-full').click()
self.browser.find_element_by_id('submit-feedback').click() self.browser.find_element_by_id('submit-feedback').click()
WebDriverWait(self.browser, 10).until(ec.url_contains('subscription/ended'))
submission_for_code = Submission.objects.get(text=code) WebDriverWait(self.browser, 10).until(self.wait_until_code_changes(code_final))
code_non_final = self._reconstruct_submission_code()
self.browser.find_element_by_class_name('final-checkbox').click()
self.browser.find_element_by_id('submit-feedback').click()
sub_url = 'subscription/' + str(self.sub_type.pk) + '/ended'
WebDriverWait(self.browser, 10).until(ec.url_contains(sub_url))
reset_browser_after_test(self.browser, self.live_server_url)
user_rev = 'rev'
password = 'p'
role = UserAccount.REVIEWER
fact.UserAccountFactory(username=user_rev, password=password, role=role)
login(self.browser, self.live_server_url, user_rev, password)
self._go_to_subscription('conflict')
code = self._reconstruct_submission_code()
self.assertEqual(code, code_non_final)
submission_for_code = Submission.objects.get(text=code_final)
self.assertEqual(self.sub_type.full_score, submission_for_code.feedback.score) self.assertEqual(self.sub_type.full_score, submission_for_code.feedback.score)
self.assertEqual(3, submission_for_code.feedback.feedback_lines.count()) self.assertEqual(3, submission_for_code.feedback.feedback_lines.count())
submission_for_code = Submission.objects.get(text=code_non_final)
self.assertEqual(0, submission_for_code.feedback.score)
self.assertEqual(1, submission_for_code.feedback.feedback_lines.count())
class TestFeedbackCreationTutor(UntestedParent.TestFeedbackCreationGeneric): class TestFeedbackCreationTutor(UntestedParent.TestFeedbackCreationGeneric):
def setUp(self): def setUp(self):
...@@ -241,5 +275,6 @@ class TestFeedbackCreationTutor(UntestedParent.TestFeedbackCreationGeneric): ...@@ -241,5 +275,6 @@ class TestFeedbackCreationTutor(UntestedParent.TestFeedbackCreationGeneric):
self.role = UserAccount.TUTOR self.role = UserAccount.TUTOR
fact.UserAccountFactory( fact.UserAccountFactory(
username=self.username, username=self.username,
password=self.password password=self.password,
role=self.role
) )