From 995f2488991c5262448611c7a5667d20a06d2214 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Tue, 15 May 2018 22:39:46 +0200 Subject: [PATCH 01/15] fix in stats comp when mean was null --- frontend/src/components/CorrectionStatistics.vue | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/CorrectionStatistics.vue b/frontend/src/components/CorrectionStatistics.vue index 2bd836a5..c6d877b2 100644 --- a/frontend/src/components/CorrectionStatistics.vue +++ b/frontend/src/components/CorrectionStatistics.vue @@ -7,7 +7,11 @@ <ul class="inline-list mx-3"> <li>Submissions per student: <span>{{statistics.submissions_per_student}}</span></li> <li>Submissions per type: <span>{{statistics.submissions_per_type}}</span></li> - <li>Curr. mean score: <span>{{statistics.current_mean_score.toFixed(2)}}</span></li> + <li>Curr. mean score: + <span> + {{statistics.current_mean_score === null ? 'N.A.' : statistics.current_mean_score.toFixed(2)}} + </span> + </li> </ul> <v-divider class="mx-2 my-2"></v-divider> <div v-for="(progress, index) in statistics.submission_type_progress" :key="index"> -- GitLab From 77eeea971d7c6ab509db0b5a26c8ef682cd2e6db Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Tue, 15 May 2018 22:59:56 +0200 Subject: [PATCH 02/15] Changes in store helper/baselayout/subscription --- frontend/src/components/BaseLayout.vue | 84 ++++++------- .../subscriptions/SubscriptionList.vue | 113 +++++++++--------- frontend/src/util/helpers.js | 4 +- 3 files changed, 103 insertions(+), 98 deletions(-) diff --git a/frontend/src/components/BaseLayout.vue b/frontend/src/components/BaseLayout.vue index c2974f03..9ab1ba03 100644 --- a/frontend/src/components/BaseLayout.vue +++ b/frontend/src/components/BaseLayout.vue @@ -84,53 +84,53 @@ </template> <script> -import { mapGetters, mapState } from 'vuex' -import {uiMut} from '@/store/modules/ui' -import { createComputedGetterSetter } from '@/util/helpers' -import UserOptions from '@/components/UserOptions' -export default { - name: 'base-layout', - components: {UserOptions}, - computed: { - ...mapGetters([ - 'gradySpeak' - ]), - ...mapState({ - username: state => state.authentication.user.username, - userRole: state => state.authentication.user.role - }), - darkMode: createComputedGetterSetter({ - path: 'ui.darkMode', - mutation: uiMut.SET_DARK_MODE - }), - darkModeUnlocked: createComputedGetterSetter({ - path: 'ui.darkModeUnlocked', - mutation: uiMut.SET_DARK_MODE_UNLOCKED - }), - mini: { - get: function () { - return this.$store.state.ui.sideBarCollapsed + import { mapGetters, mapState } from 'vuex' + import {uiMut} from '@/store/modules/ui' + import { mapStateToComputedGetterSetter } from '@/util/helpers' + export default { + name: 'base-layout', + computed: { + ...mapGetters([ + 'gradySpeak' + ]), + ...mapState({ + username: state => state.authentication.username, + userRole: state => state.authentication.userRole + }), + ...mapStateToComputedGetterSetter({ + pathPrefix: 'ui', + items: [ + { + name: 'darkMode', + mutation: uiMut.SET_DARK_MODE + }, + { + name: 'darkModeUnlocked', + mutation: uiMut.SET_DARK_MODE_UNLOCKED + }, + { + name: 'mini', + path: 'sideBarCollapsed', + mutation: uiMut.SET_SIDEBAR_COLLAPSED + } + ] + }), + production () { + return process.env.NODE_ENV === 'production' }, - set: function (collapsed) { - this.$store.commit(uiMut.SET_SIDEBAR_COLLAPSED, collapsed) + productionBrandUrl () { + return `https://${window.location.host}/static/img/brand.png` } }, - production () { - return process.env.NODE_ENV === 'production' - }, - productionBrandUrl () { - return `https://${window.location.host}/static/img/brand.png` - } - }, - methods: { - logout () { - this.$store.dispatch('logout') - }, - logFeedbackClick () { - this.darkModeUnlocked = true + methods: { + logout () { + this.$store.dispatch('logout') + }, + logFeedbackClick () { + this.darkModeUnlocked = true + } } } -} </script> <style scoped> diff --git a/frontend/src/components/subscriptions/SubscriptionList.vue b/frontend/src/components/subscriptions/SubscriptionList.vue index 4ce05b34..cd63a0c0 100644 --- a/frontend/src/components/subscriptions/SubscriptionList.vue +++ b/frontend/src/components/subscriptions/SubscriptionList.vue @@ -2,7 +2,7 @@ <v-card> <v-toolbar color="teal" :dense="sidebar"> <v-toolbar-side-icon><v-icon>assignment</v-icon></v-toolbar-side-icon> - <v-toolbar-title v-if="!sidebar || (sidebar && !sideBarCollapsed)" style="min-width: fit-content;"> + <v-toolbar-title v-if="showDetail" style="min-width: fit-content;"> Tasks </v-toolbar-title> <v-spacer/> @@ -16,7 +16,7 @@ /> </v-btn> </v-toolbar> - <v-tabs grow color="teal lighten-1" v-model="selectedStage"> + <v-tabs grow color="teal lighten-1" v-model="selectedStage" v-if="showDetail"> <v-tab v-for="(item, i) in stagesReadable" :key="i"> {{item}} </v-tab> @@ -28,62 +28,65 @@ </template> <script> -import {mapGetters, mapActions, mapState} from 'vuex' -import SubscriptionCreation from '@/components/subscriptions/SubscriptionCreation' -import SubscriptionForList from '@/components/subscriptions/SubscriptionForList' -import SubscriptionsForStage from '@/components/subscriptions/SubscriptionsForStage' + import {mapGetters, mapActions, mapState} from 'vuex' + import SubscriptionCreation from '@/components/subscriptions/SubscriptionCreation' + import SubscriptionForList from '@/components/subscriptions/SubscriptionForList' + import SubscriptionsForStage from '@/components/subscriptions/SubscriptionsForStage' -export default { - components: { - SubscriptionsForStage, - SubscriptionForList, - SubscriptionCreation}, - name: 'subscription-list', - props: { - sidebar: { - type: Boolean, - default: false + export default { + components: { + SubscriptionsForStage, + SubscriptionForList, + SubscriptionCreation}, + name: 'subscription-list', + props: { + sidebar: { + type: Boolean, + default: false + } + }, + data () { + return { + selectedStage: null, + updating: false + } + }, + computed: { + ...mapState({ + sideBarCollapsed: state => state.ui.sideBarCollapsed + }), + ...mapGetters({ + subscriptions: 'getSubscriptionsGroupedByType', + stages: 'availableStages', + stagesReadable: 'availableStagesReadable' + }), + showDetail () { + return !this.sidebar || (this.sidebar && !this.sideBarCollapsed) + } + }, + methods: { + ...mapActions([ + 'updateSubmissionTypes', + 'getCurrentAssignment', + 'getExamTypes', + 'subscribeToAll', + 'cleanAssignmentsFromSubscriptions' + ]), + async getSubscriptions () { + this.updating = true + const subscriptions = await this.$store.dispatch('getSubscriptions') + this.updating = false + return subscriptions + } + }, + created () { + const typesAndSubscriptions = [this.updateSubmissionTypes(), this.getSubscriptions()] + Promise.all(typesAndSubscriptions).then(() => { + this.subscribeToAll() + this.cleanAssignmentsFromSubscriptions() + }) } - }, - data () { - return { - selectedStage: null, - updating: false - } - }, - computed: { - ...mapState({ - sideBarCollapsed: state => state.ui.sideBarCollapsed - }), - ...mapGetters({ - subscriptions: 'getSubscriptionsGroupedByType', - stages: 'availableStages', - stagesReadable: 'availableStagesReadable' - }) - }, - methods: { - ...mapActions([ - 'updateSubmissionTypes', - 'getCurrentAssignment', - 'getExamTypes', - 'subscribeToAll', - 'cleanAssignmentsFromSubscriptions' - ]), - async getSubscriptions () { - this.updating = true - const subscriptions = await this.$store.dispatch('getSubscriptions') - this.updating = false - return subscriptions - } - }, - created () { - const typesAndSubscriptions = [this.updateSubmissionTypes(), this.getSubscriptions()] - Promise.all(typesAndSubscriptions).then(() => { - this.subscribeToAll() - this.cleanAssignmentsFromSubscriptions() - }) } -} </script> <style scoped> diff --git a/frontend/src/util/helpers.js b/frontend/src/util/helpers.js index c18c724f..a582d317 100644 --- a/frontend/src/util/helpers.js +++ b/frontend/src/util/helpers.js @@ -51,7 +51,9 @@ export function createComputedGetterSetter ({path, mutation, namespace}) { */ export function mapStateToComputedGetterSetter ({namespace = '', pathPrefix = '', items = []}) { return items.reduce((acc, curr) => { - let path = pathPrefix ? `${pathPrefix}.${curr.path}` : curr.path + // if no path is give, use name + const itemPath = curr.path || curr.name + const path = pathPrefix ? `${pathPrefix}.${itemPath}` : itemPath acc[curr.name] = createComputedGetterSetter({...curr, path, namespace}) return acc }, {}) -- GitLab From 1c790526da1cc8e5e2deec7dc8086bd8dbfc6e36 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Tue, 15 May 2018 23:23:06 +0200 Subject: [PATCH 03/15] WelcomeJumbotron now hidable --- frontend/src/components/WelcomeJumbotron.vue | 37 ++++++++++++++++---- frontend/src/store/modules/ui.js | 9 +++-- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/WelcomeJumbotron.vue b/frontend/src/components/WelcomeJumbotron.vue index d3d9151f..4c140e5c 100644 --- a/frontend/src/components/WelcomeJumbotron.vue +++ b/frontend/src/components/WelcomeJumbotron.vue @@ -1,5 +1,10 @@ <template> - <v-jumbotron :gradient="gradient" dark class="elevation-10"> + <v-jumbotron :gradient="gradient" dark class="elevation-10" v-if="showJumbotron"> + <v-btn @click="hide" icon class="hide-btn"> + <v-icon> + close + </v-icon> + </v-btn> <v-container fill-height> <v-layout align-center> <v-flex> @@ -26,14 +31,28 @@ </template> <script> -export default { - name: 'welcome-jumbotron', - data () { - return { - gradient: 'to bottom, #1A237E, #5753DD' + import { createComputedGetterSetter } from '@/util/helpers' + import { uiMut } from '@/store/modules/ui' + + export default { + name: 'welcome-jumbotron', + data () { + return { + gradient: 'to bottom, #1A237E, #5753DD' + } + }, + computed: { + showJumbotron: createComputedGetterSetter({ + path: 'ui.showJumbotron', + mutation: uiMut.SET_SHOW_JUMBOTRON + }) + }, + methods: { + hide () { + this.showJumbotron = false + } } } -} </script> <style scoped> @@ -41,4 +60,8 @@ export default { color: lightgrey; text-decoration: none; } + .hide-btn { + position: absolute; + right: 0px; + } </style> diff --git a/frontend/src/store/modules/ui.js b/frontend/src/store/modules/ui.js index df08d683..2cecc601 100644 --- a/frontend/src/store/modules/ui.js +++ b/frontend/src/store/modules/ui.js @@ -3,14 +3,16 @@ function initialState () { return { sideBarCollapsed: false, darkMode: false, - darkModeUnlocked: false + darkModeUnlocked: false, + showJumbotron: true } } export const uiMut = Object.freeze({ SET_SIDEBAR_COLLAPSED: 'SET_SIDEBAR_COLLAPSED', SET_DARK_MODE: 'SET_DARK_MODE', - SET_DARK_MODE_UNLOCKED: 'SET_DARK_MODE_UNLOCKED' + SET_DARK_MODE_UNLOCKED: 'SET_DARK_MODE_UNLOCKED', + SET_SHOW_JUMBOTRON: 'SET_SHOW_JUMBOTRON' }) const ui = { @@ -24,6 +26,9 @@ const ui = { }, [uiMut.SET_DARK_MODE_UNLOCKED] (state, val) { state.darkModeUnlocked = val + }, + [uiMut.SET_SHOW_JUMBOTRON] (state, val) { + state.showJumbotron = val } } } -- GitLab From b479780bd483dc8784504b2a71165c3ad599e757 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sun, 5 Aug 2018 12:33:32 +0200 Subject: [PATCH 04/15] Fixed some button positioning --- frontend/src/components/BaseLayout.vue | 91 +++++++------- frontend/src/components/WelcomeJumbotron.vue | 43 ++++--- .../submission_notes/base/FeedbackComment.vue | 7 +- .../subscriptions/SubscriptionList.vue | 112 +++++++++--------- 4 files changed, 127 insertions(+), 126 deletions(-) diff --git a/frontend/src/components/BaseLayout.vue b/frontend/src/components/BaseLayout.vue index 9ab1ba03..87a14851 100644 --- a/frontend/src/components/BaseLayout.vue +++ b/frontend/src/components/BaseLayout.vue @@ -84,53 +84,56 @@ </template> <script> - import { mapGetters, mapState } from 'vuex' - import {uiMut} from '@/store/modules/ui' - import { mapStateToComputedGetterSetter } from '@/util/helpers' - export default { - name: 'base-layout', - computed: { - ...mapGetters([ - 'gradySpeak' - ]), - ...mapState({ - username: state => state.authentication.username, - userRole: state => state.authentication.userRole - }), - ...mapStateToComputedGetterSetter({ - pathPrefix: 'ui', - items: [ - { - name: 'darkMode', - mutation: uiMut.SET_DARK_MODE - }, - { - name: 'darkModeUnlocked', - mutation: uiMut.SET_DARK_MODE_UNLOCKED - }, - { - name: 'mini', - path: 'sideBarCollapsed', - mutation: uiMut.SET_SIDEBAR_COLLAPSED - } - ] - }), - production () { - return process.env.NODE_ENV === 'production' - }, - productionBrandUrl () { - return `https://${window.location.host}/static/img/brand.png` - } +import { mapGetters, mapState } from 'vuex' +import {uiMut} from '@/store/modules/ui' +import { mapStateToComputedGetterSetter } from '@/util/helpers' +import UserOptions from '@/components/UserOptions' + +export default { + name: 'base-layout', + components: {UserOptions}, + computed: { + ...mapGetters([ + 'gradySpeak' + ]), + ...mapState({ + username: state => state.authentication.user.username, + userRole: state => state.authentication.user.role + }), + ...mapStateToComputedGetterSetter({ + pathPrefix: 'ui', + items: [ + { + name: 'darkMode', + mutation: uiMut.SET_DARK_MODE + }, + { + name: 'darkModeUnlocked', + mutation: uiMut.SET_DARK_MODE_UNLOCKED + }, + { + name: 'mini', + path: 'sideBarCollapsed', + mutation: uiMut.SET_SIDEBAR_COLLAPSED + } + ] + }), + production () { + return process.env.NODE_ENV === 'production' + }, + productionBrandUrl () { + return `https://${window.location.host}/static/img/brand.png` + } + }, + methods: { + logout () { + this.$store.dispatch('logout') }, - methods: { - logout () { - this.$store.dispatch('logout') - }, - logFeedbackClick () { - this.darkModeUnlocked = true - } + logFeedbackClick () { + this.darkModeUnlocked = true } } +} </script> <style scoped> diff --git a/frontend/src/components/WelcomeJumbotron.vue b/frontend/src/components/WelcomeJumbotron.vue index 4c140e5c..687274a6 100644 --- a/frontend/src/components/WelcomeJumbotron.vue +++ b/frontend/src/components/WelcomeJumbotron.vue @@ -1,6 +1,6 @@ <template> <v-jumbotron :gradient="gradient" dark class="elevation-10" v-if="showJumbotron"> - <v-btn @click="hide" icon class="hide-btn"> + <v-btn @click="hide" icon class="hide-btn" absolute> <v-icon> close </v-icon> @@ -31,28 +31,28 @@ </template> <script> - import { createComputedGetterSetter } from '@/util/helpers' - import { uiMut } from '@/store/modules/ui' +import { createComputedGetterSetter } from '@/util/helpers' +import { uiMut } from '@/store/modules/ui' - export default { - name: 'welcome-jumbotron', - data () { - return { - gradient: 'to bottom, #1A237E, #5753DD' - } - }, - computed: { - showJumbotron: createComputedGetterSetter({ - path: 'ui.showJumbotron', - mutation: uiMut.SET_SHOW_JUMBOTRON - }) - }, - methods: { - hide () { - this.showJumbotron = false - } +export default { + name: 'welcome-jumbotron', + data () { + return { + gradient: 'to bottom, #1A237E, #5753DD' + } + }, + computed: { + showJumbotron: createComputedGetterSetter({ + path: 'ui.showJumbotron', + mutation: uiMut.SET_SHOW_JUMBOTRON + }) + }, + methods: { + hide () { + this.showJumbotron = false } } +} </script> <style scoped> @@ -61,7 +61,6 @@ text-decoration: none; } .hide-btn { - position: absolute; - right: 0px; + right: 0; } </style> diff --git a/frontend/src/components/submission_notes/base/FeedbackComment.vue b/frontend/src/components/submission_notes/base/FeedbackComment.vue index 93591485..c45d38a9 100644 --- a/frontend/src/components/submission_notes/base/FeedbackComment.vue +++ b/frontend/src/components/submission_notes/base/FeedbackComment.vue @@ -21,7 +21,7 @@ </div> <div class="message">{{text}}</div> <v-btn - flat icon + flat icon absolute class="delete-button" v-if="deletable" @click.stop="toggleDeleteComment" @@ -144,9 +144,8 @@ export default { white-space: pre-wrap; } .delete-button { - position: absolute; - bottom: -20px; - left: -50px; + bottom: -15px; + left: -42px; } .comment-created { position: absolute; diff --git a/frontend/src/components/subscriptions/SubscriptionList.vue b/frontend/src/components/subscriptions/SubscriptionList.vue index cd63a0c0..2e8b7f72 100644 --- a/frontend/src/components/subscriptions/SubscriptionList.vue +++ b/frontend/src/components/subscriptions/SubscriptionList.vue @@ -28,65 +28,65 @@ </template> <script> - import {mapGetters, mapActions, mapState} from 'vuex' - import SubscriptionCreation from '@/components/subscriptions/SubscriptionCreation' - import SubscriptionForList from '@/components/subscriptions/SubscriptionForList' - import SubscriptionsForStage from '@/components/subscriptions/SubscriptionsForStage' +import {mapGetters, mapActions, mapState} from 'vuex' +import SubscriptionCreation from '@/components/subscriptions/SubscriptionCreation' +import SubscriptionForList from '@/components/subscriptions/SubscriptionForList' +import SubscriptionsForStage from '@/components/subscriptions/SubscriptionsForStage' - export default { - components: { - SubscriptionsForStage, - SubscriptionForList, - SubscriptionCreation}, - name: 'subscription-list', - props: { - sidebar: { - type: Boolean, - default: false - } - }, - data () { - return { - selectedStage: null, - updating: false - } - }, - computed: { - ...mapState({ - sideBarCollapsed: state => state.ui.sideBarCollapsed - }), - ...mapGetters({ - subscriptions: 'getSubscriptionsGroupedByType', - stages: 'availableStages', - stagesReadable: 'availableStagesReadable' - }), - showDetail () { - return !this.sidebar || (this.sidebar && !this.sideBarCollapsed) - } - }, - methods: { - ...mapActions([ - 'updateSubmissionTypes', - 'getCurrentAssignment', - 'getExamTypes', - 'subscribeToAll', - 'cleanAssignmentsFromSubscriptions' - ]), - async getSubscriptions () { - this.updating = true - const subscriptions = await this.$store.dispatch('getSubscriptions') - this.updating = false - return subscriptions - } - }, - created () { - const typesAndSubscriptions = [this.updateSubmissionTypes(), this.getSubscriptions()] - Promise.all(typesAndSubscriptions).then(() => { - this.subscribeToAll() - this.cleanAssignmentsFromSubscriptions() - }) +export default { + components: { + SubscriptionsForStage, + SubscriptionForList, + SubscriptionCreation}, + name: 'subscription-list', + props: { + sidebar: { + type: Boolean, + default: false } + }, + data () { + return { + selectedStage: null, + updating: false + } + }, + computed: { + ...mapState({ + sideBarCollapsed: state => state.ui.sideBarCollapsed + }), + ...mapGetters({ + subscriptions: 'getSubscriptionsGroupedByType', + stages: 'availableStages', + stagesReadable: 'availableStagesReadable' + }), + showDetail () { + return !this.sidebar || (this.sidebar && !this.sideBarCollapsed) + } + }, + methods: { + ...mapActions([ + 'updateSubmissionTypes', + 'getCurrentAssignment', + 'getExamTypes', + 'subscribeToAll', + 'cleanAssignmentsFromSubscriptions' + ]), + async getSubscriptions () { + this.updating = true + const subscriptions = await this.$store.dispatch('getSubscriptions') + this.updating = false + return subscriptions + } + }, + created () { + const typesAndSubscriptions = [this.updateSubmissionTypes(), this.getSubscriptions()] + Promise.all(typesAndSubscriptions).then(() => { + this.subscribeToAll() + this.cleanAssignmentsFromSubscriptions() + }) } +} </script> <style scoped> -- GitLab From 1a025710b6d8493aae2c733cb604f17b66e01265 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sun, 5 Aug 2018 12:37:08 +0200 Subject: [PATCH 05/15] Fixed static files in prod --- frontend/vue.config.js | 1 + grady/settings/default.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/vue.config.js b/frontend/vue.config.js index 78bc4df2..6c2485df 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -3,6 +3,7 @@ const path = require('path') const projectRoot = path.resolve(__dirname) module.exports = { + assetsDir: 'static', configureWebpack: { resolve: { alias: { diff --git a/grady/settings/default.py b/grady/settings/default.py index 8099ca89..eb220958 100644 --- a/grady/settings/default.py +++ b/grady/settings/default.py @@ -116,7 +116,7 @@ STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ) STATICFILES_DIRS = ( - 'frontend/dist/', + 'frontend/dist/static', ) -- GitLab From 3ccd714822138361370b8de759d67780ca4204f4 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sun, 5 Aug 2018 14:06:29 +0200 Subject: [PATCH 06/15] Added localhost to webpack devserver allowed hosts --- frontend/vue.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/vue.config.js b/frontend/vue.config.js index 6c2485df..29c09438 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -4,6 +4,9 @@ const projectRoot = path.resolve(__dirname) module.exports = { assetsDir: 'static', + devServer: { + allowedHosts: ['localhost'] + }, configureWebpack: { resolve: { alias: { -- GitLab From 7ab2e07359692767df7c994a3c5eb063c159dc5f Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sun, 5 Aug 2018 14:07:04 +0200 Subject: [PATCH 07/15] Tutormanager migration --- core/migrations/0010_auto_20180805_1139.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 core/migrations/0010_auto_20180805_1139.py diff --git a/core/migrations/0010_auto_20180805_1139.py b/core/migrations/0010_auto_20180805_1139.py new file mode 100644 index 00000000..622d7e5a --- /dev/null +++ b/core/migrations/0010_auto_20180805_1139.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1 on 2018-08-05 11:39 + +import core.models +import django.contrib.auth.models +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_auto_20180320_2335'), + ] + + operations = [ + migrations.AlterModelManagers( + name='useraccount', + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ('tutors', core.models.TutorManager()), + ], + ), + ] -- GitLab From 8fe74889c5cb811959e10aa142bb94275f6deeab Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sun, 5 Aug 2018 20:34:48 +0200 Subject: [PATCH 08/15] Fixed some button positioning / dialog widths --- frontend/src/components/AutoLogout.vue | 2 +- frontend/src/components/submission_notes/base/CommentForm.vue | 4 ++-- .../src/components/submission_notes/base/FeedbackComment.vue | 2 +- frontend/src/pages/Login.vue | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/AutoLogout.vue b/frontend/src/components/AutoLogout.vue index 76e69552..3c29cf12 100644 --- a/frontend/src/components/AutoLogout.vue +++ b/frontend/src/components/AutoLogout.vue @@ -1,7 +1,7 @@ <template> <v-dialog persistent - width="fit-content" + max-width="30%" v-model="logoutDialog" > <v-card> diff --git a/frontend/src/components/submission_notes/base/CommentForm.vue b/frontend/src/components/submission_notes/base/CommentForm.vue index a8c1949d..603f4398 100644 --- a/frontend/src/components/submission_notes/base/CommentForm.vue +++ b/frontend/src/components/submission_notes/base/CommentForm.vue @@ -1,6 +1,6 @@ <template> <div> - <v-text-field + <v-textarea name="feedback-input" label="Please provide your feedback here" v-model="currentFeedback" @@ -8,7 +8,7 @@ @keyup.esc="collapseTextField" @focus="selectInput($event)" rows="2" - textarea + outline autofocus auto-grow hide-details diff --git a/frontend/src/components/submission_notes/base/FeedbackComment.vue b/frontend/src/components/submission_notes/base/FeedbackComment.vue index c45d38a9..5b29b56c 100644 --- a/frontend/src/components/submission_notes/base/FeedbackComment.vue +++ b/frontend/src/components/submission_notes/base/FeedbackComment.vue @@ -144,7 +144,7 @@ export default { white-space: pre-wrap; } .delete-button { - bottom: -15px; + bottom: -12px; left: -42px; } .comment-created { diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index 972b08b5..0988645e 100644 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -1,7 +1,7 @@ <template> <v-container fill-height> <v-layout align-center justify-center> - <v-dialog v-model="registerDialog" max-width="fit-content" class="pa-4"> + <v-dialog v-model="registerDialog" class="pa-4" max-width="30%"> <register-dialog @registered="registered($event)"/> </v-dialog> <v-flex text-xs-center xs8 sm6 md4 lg2> -- GitLab From ff996d22b6be648be0537e13ef6b01939722ce5a Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sun, 5 Aug 2018 21:34:44 +0200 Subject: [PATCH 09/15] Fixed Export button closes #114 --- frontend/src/components/DataExport.vue | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/src/components/DataExport.vue b/frontend/src/components/DataExport.vue index 2b3c5868..451fd950 100644 --- a/frontend/src/components/DataExport.vue +++ b/frontend/src/components/DataExport.vue @@ -163,8 +163,4 @@ export default { </script> <style scoped> - #export-link { - color: #000; - text-decoration: none; - } </style> -- GitLab From 200750722241590cff650161b4d23728a856c00a Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sun, 5 Aug 2018 21:35:44 +0200 Subject: [PATCH 10/15] Started typing js files --- frontend/@types/v-clipboard/index.d.ts | 1 + frontend/src/main.ts | 4 ++-- frontend/src/router/index.ts | 18 ++++++++++-------- frontend/src/store/getters.js | 2 +- frontend/tsconfig.json | 8 ++------ 5 files changed, 16 insertions(+), 17 deletions(-) create mode 100644 frontend/@types/v-clipboard/index.d.ts diff --git a/frontend/@types/v-clipboard/index.d.ts b/frontend/@types/v-clipboard/index.d.ts new file mode 100644 index 00000000..f2dd4459 --- /dev/null +++ b/frontend/@types/v-clipboard/index.d.ts @@ -0,0 +1 @@ +declare module 'v-clipboard'; \ No newline at end of file diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 5576736f..ad81436e 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -4,13 +4,13 @@ import router from './router/index' import store from './store/store' import Vuetify from 'vuetify' import Notifications from 'vue-notification' -import Cliboard from 'v-clipboard' +import Clipboard from 'v-clipboard' import 'vuetify/dist/vuetify.min.css' import 'highlight.js/styles/atom-one-light.css' Vue.use(Vuetify) -Vue.use(Cliboard) +Vue.use(Clipboard) Vue.use(Notifications) Vue.config.productionTip = false diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index e780522f..25b1aeee 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,5 +1,5 @@ import Vue from 'vue' -import Router from 'vue-router' +import Router, {RawLocation, Route, NavigationGuard} from 'vue-router' import Login from '@/pages/Login.vue' import StudentSubmissionPage from '@/pages/student/StudentSubmissionPage.vue' import StudentOverviewPage from '@/pages/reviewer/StudentOverviewPage.vue' @@ -19,7 +19,9 @@ import store from '@/store/store' Vue.use(Router) -function denyAccess (next, redirect) { +type rerouteFunc = (to?: RawLocation | false | ((vm: Vue) => any) | void) => void + +function denyAccess (next: rerouteFunc, redirect: Route) { next(redirect.path) VueInstance.$notify({ title: 'Access denied', @@ -28,23 +30,23 @@ function denyAccess (next, redirect) { }) } -function tutorOrReviewerOnly (to, from, next) { +let tutorOrReviewerOnly: NavigationGuard = function (to, from, next) { if (store.getters.isTutorOrReviewer) { next() } else { - denyAccess(next, from.path) + denyAccess(next, from) } } -function reviewerOnly (to, from, next) { +let reviewerOnly: NavigationGuard = function (to, from, next) { if (store.getters.isReviewer) { next() } else { - denyAccess(next, from.path) + denyAccess(next, from) } } -function studentOnly (to, from, next) { +let studentOnly: NavigationGuard = function (to, from, next) { if (store.getters.isStudent) { next() } else { @@ -52,7 +54,7 @@ function studentOnly (to, from, next) { } } -function checkLoggedIn (to, from, next) { +let checkLoggedIn: NavigationGuard = function (to, from, next) { if (store.getters.isLoggedIn) { next() } else { diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.js index 5663746c..3cdc0cbe 100644 --- a/frontend/src/store/getters.js +++ b/frontend/src/store/getters.js @@ -1,7 +1,7 @@ const getters = { corrected (state) { return state.statistics.submission_type_progress.every(progress => { - return progress.percentage === 100 + return progress.feedback_final === progress.submission_count }) }, getSubmission: state => pk => { diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c1700466..c19146b1 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "esnext", "module": "esnext", - "strict": false, + "strict": true, "jsx": "preserve", "importHelpers": true, "moduleResolution": "node", @@ -10,11 +10,6 @@ "esModuleInterop": true, "sourceMap": true, "baseUrl": ".", - "types": [ - "node", - "mocha", - "chai" - ], "paths": { "@/*": [ "src/*" @@ -28,6 +23,7 @@ ] }, "include": [ + "@types/", "src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", -- GitLab From f59e38392cbce9ab6d49c69a745e2a85ff9784c8 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Sun, 5 Aug 2018 23:00:43 +0200 Subject: [PATCH 11/15] Renamed store files to .ts --- frontend/src/{api.js => api.ts} | 0 frontend/src/store/{actions.js => actions.ts} | 0 frontend/src/store/{getters.js => getters.ts} | 0 frontend/src/store/{grady_speak.js => grady_speak.ts} | 0 .../src/store/modules/{authentication.js => authentication.ts} | 0 .../{feedback-search-options.js => feedback-search-options.ts} | 0 .../feedback_list/{feedback-table.js => feedback-table.ts} | 0 frontend/src/store/modules/{student-page.js => student-page.ts} | 0 .../store/modules/{submission-notes.js => submission-notes.ts} | 0 frontend/src/store/modules/{subscriptions.js => subscriptions.ts} | 0 frontend/src/store/modules/{ui.js => ui.ts} | 0 frontend/src/store/{mutations.js => mutations.ts} | 0 .../{lastInteractionPlugin.js => lastInteractionPlugin.ts} | 0 frontend/src/util/{helpers.js => helpers.ts} | 0 14 files changed, 0 insertions(+), 0 deletions(-) rename frontend/src/{api.js => api.ts} (100%) rename frontend/src/store/{actions.js => actions.ts} (100%) rename frontend/src/store/{getters.js => getters.ts} (100%) rename frontend/src/store/{grady_speak.js => grady_speak.ts} (100%) rename frontend/src/store/modules/{authentication.js => authentication.ts} (100%) rename frontend/src/store/modules/feedback_list/{feedback-search-options.js => feedback-search-options.ts} (100%) rename frontend/src/store/modules/feedback_list/{feedback-table.js => feedback-table.ts} (100%) rename frontend/src/store/modules/{student-page.js => student-page.ts} (100%) rename frontend/src/store/modules/{submission-notes.js => submission-notes.ts} (100%) rename frontend/src/store/modules/{subscriptions.js => subscriptions.ts} (100%) rename frontend/src/store/modules/{ui.js => ui.ts} (100%) rename frontend/src/store/{mutations.js => mutations.ts} (100%) rename frontend/src/store/plugins/{lastInteractionPlugin.js => lastInteractionPlugin.ts} (100%) rename frontend/src/util/{helpers.js => helpers.ts} (100%) diff --git a/frontend/src/api.js b/frontend/src/api.ts similarity index 100% rename from frontend/src/api.js rename to frontend/src/api.ts diff --git a/frontend/src/store/actions.js b/frontend/src/store/actions.ts similarity index 100% rename from frontend/src/store/actions.js rename to frontend/src/store/actions.ts diff --git a/frontend/src/store/getters.js b/frontend/src/store/getters.ts similarity index 100% rename from frontend/src/store/getters.js rename to frontend/src/store/getters.ts diff --git a/frontend/src/store/grady_speak.js b/frontend/src/store/grady_speak.ts similarity index 100% rename from frontend/src/store/grady_speak.js rename to frontend/src/store/grady_speak.ts diff --git a/frontend/src/store/modules/authentication.js b/frontend/src/store/modules/authentication.ts similarity index 100% rename from frontend/src/store/modules/authentication.js rename to frontend/src/store/modules/authentication.ts diff --git a/frontend/src/store/modules/feedback_list/feedback-search-options.js b/frontend/src/store/modules/feedback_list/feedback-search-options.ts similarity index 100% rename from frontend/src/store/modules/feedback_list/feedback-search-options.js rename to frontend/src/store/modules/feedback_list/feedback-search-options.ts diff --git a/frontend/src/store/modules/feedback_list/feedback-table.js b/frontend/src/store/modules/feedback_list/feedback-table.ts similarity index 100% rename from frontend/src/store/modules/feedback_list/feedback-table.js rename to frontend/src/store/modules/feedback_list/feedback-table.ts diff --git a/frontend/src/store/modules/student-page.js b/frontend/src/store/modules/student-page.ts similarity index 100% rename from frontend/src/store/modules/student-page.js rename to frontend/src/store/modules/student-page.ts diff --git a/frontend/src/store/modules/submission-notes.js b/frontend/src/store/modules/submission-notes.ts similarity index 100% rename from frontend/src/store/modules/submission-notes.js rename to frontend/src/store/modules/submission-notes.ts diff --git a/frontend/src/store/modules/subscriptions.js b/frontend/src/store/modules/subscriptions.ts similarity index 100% rename from frontend/src/store/modules/subscriptions.js rename to frontend/src/store/modules/subscriptions.ts diff --git a/frontend/src/store/modules/ui.js b/frontend/src/store/modules/ui.ts similarity index 100% rename from frontend/src/store/modules/ui.js rename to frontend/src/store/modules/ui.ts diff --git a/frontend/src/store/mutations.js b/frontend/src/store/mutations.ts similarity index 100% rename from frontend/src/store/mutations.js rename to frontend/src/store/mutations.ts diff --git a/frontend/src/store/plugins/lastInteractionPlugin.js b/frontend/src/store/plugins/lastInteractionPlugin.ts similarity index 100% rename from frontend/src/store/plugins/lastInteractionPlugin.js rename to frontend/src/store/plugins/lastInteractionPlugin.ts diff --git a/frontend/src/util/helpers.js b/frontend/src/util/helpers.ts similarity index 100% rename from frontend/src/util/helpers.js rename to frontend/src/util/helpers.ts -- GitLab From 9de3c6843cd9f9ef2dca317d88e952dfc5f6baf6 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Mon, 6 Aug 2018 14:22:15 +0200 Subject: [PATCH 12/15] Started added typings in store files --- frontend/src/api.ts | 39 +++++++------- .../feedback_list/FeedbackSearchOptions.vue | 6 --- .../RouteChangeConfirmation.vue | 2 - frontend/src/store/modules/authentication.ts | 53 +++++++++++++------ frontend/src/util/helpers.ts | 46 +++++++++++----- frontend/tsconfig.json | 4 +- frontend/vue.config.js | 3 +- 7 files changed, 94 insertions(+), 59 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index d5965da6..029722cd 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1,6 +1,7 @@ import axios from 'axios' +import {Credentials} from "@/store/modules/authentication"; -function addFieldsToUrl ({url, fields = []}) { +function addFieldsToUrl ({url, fields = []}: {url: string, fields?: string[]}) { return fields.length > 0 ? url + '?fields=pk,' + fields : url } @@ -22,27 +23,27 @@ let ax = axios.create({ } } -export async function registerTutor (credentials) { +export async function registerTutor (credentials: Credentials) { return ax.post('/api/tutor/register/', credentials) } -export async function fetchJWT (credentials) { - const token = (await ax.post('/api/get-token/', credentials)).data.token +export async function fetchJWT (credentials: Credentials): Promise<string> { + const token: string = (await ax.post('/api/get-token/', credentials)).data.token ax.defaults.headers['Authorization'] = `JWT ${token}` return token } -export async function refreshJWT (token) { - const newToken = (await ax.post('/api/refresh-token/', {token})).data.token +export async function refreshJWT (token: string): Promise<string> { + const newToken: string = (await ax.post('/api/refresh-token/', {token})).data.token ax.defaults.headers['Authorization'] = `JWT ${newToken}` return newToken } -export async function fetchJWTTimeDelta () { +export async function fetchJWTTimeDelta (): Promise<number> { return (await ax.get('/api/jwt-time-delta/')).data.timeDelta } -export async function fetchUserRole () { +export async function fetchUserRole (): Promise<string> { return (await ax.get('/api/user-role/')).data.role } @@ -54,11 +55,11 @@ export async function fetchStudentSubmissions () { return (await ax.get('/api/student-submissions/')).data } -export async function fetchSubmissionFeedbackTests ({pk}) { +export async function fetchSubmissionFeedbackTests ({pk}: {pk: string}) { return (await ax.get(`/api/submission/${pk}/`)).data } -export async function fetchAllStudents (fields = []) { +export async function fetchAllStudents (fields: string[] = []) { const url = addFieldsToUrl({ url: '/api/student/', fields @@ -66,7 +67,8 @@ export async function fetchAllStudents (fields = []) { return (await ax.get(url)).data } -export async function fetchStudent ({pk, fields = []}) { +export async function fetchStudent ({pk, fields = []}: + {pk: string, fields?: string[]}) { const url = addFieldsToUrl({ url: `/api/student/${pk}/`, fields @@ -74,7 +76,7 @@ export async function fetchStudent ({pk, fields = []}) { return (await ax.get(url)).data } -export async function fetchAllTutors (fields = []) { +export async function fetchAllTutors (fields: string[] = []) { const url = addFieldsToUrl({ url: '/api/tutor/', fields @@ -86,16 +88,16 @@ export async function fetchSubscriptions () { return (await ax.get('/api/subscription/')).data } -export async function deactivateSubscription ({pk}) { +export async function deactivateSubscription ({pk}: {pk: string}) { const url = `/api/subscription/${pk}/` return (await ax.delete(url)).data } -export async function fetchSubscription (subscriptionPk) { +export async function fetchSubscription (subscriptionPk: string) { return (await ax.get(`/api/subscription/${subscriptionPk}/`)).data } -export async function fetchAllFeedback (fields = []) { +export async function fetchAllFeedback (fields: string[] = []) { const url = addFieldsToUrl({ url: '/api/feedback/', fields @@ -108,7 +110,8 @@ export async function fetchFeedback ({ofSubmission}) { return (await ax.get(url)).data } -export async function fetchExamType ({examPk, fields = []}) { +export async function fetchExamType ({examPk, fields = []}: + {examPk: string, fields?: string[]}) { const url = addFieldsToUrl({ url: `/api/examtype/${examPk !== undefined ? examPk + '/' : ''}`, fields}) @@ -189,7 +192,7 @@ export async function deactivateAllStudentAccess () { return ax.post('/api/student/deactivate/') } -export async function changePassword (userPk, data) { +export async function changePassword (userPk: string, data) { return ax.patch(`/api/user/${userPk}/change_password/`, data) } @@ -197,7 +200,7 @@ export async function getOwnUser () { return (await ax.get('/api/user/me/')).data } -export async function changeActiveForUser (userPk, active) { +export async function changeActiveForUser (userPk: string, active: boolean) { return (await ax.patch(`/api/user/${userPk}/change_active/`, {'is_active': active})).data } diff --git a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue index 03e7d84f..c5ec661f 100644 --- a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue +++ b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue @@ -88,32 +88,26 @@ export default { items: [ { name: 'showFinal', - path: 'showFinal', mutation: feedbackSearchOptsMut.SET_SHOW_FINAL }, { name: 'searchOtherUserComments', - path: 'searchOtherUserComments', mutation: feedbackSearchOptsMut.SET_SEARCH_OTHER_USER_COMMENTS }, { name: 'caseSensitive', - path: 'caseSensitive', mutation: feedbackSearchOptsMut.SET_CASE_SENSITIVE }, { name: 'useRegex', - path: 'useRegex', mutation: feedbackSearchOptsMut.SET_USE_REGEX }, { name: 'filterByTutors', - path: 'filterByTutors', mutation: feedbackSearchOptsMut.SET_FILTER_BY_TUTORS }, { name: 'filterByStage', - path: 'filterByStage', mutation: feedbackSearchOptsMut.SET_FILTER_BY_STAGE } diff --git a/frontend/src/components/submission_notes/RouteChangeConfirmation.vue b/frontend/src/components/submission_notes/RouteChangeConfirmation.vue index ce5a4b26..362980ee 100644 --- a/frontend/src/components/submission_notes/RouteChangeConfirmation.vue +++ b/frontend/src/components/submission_notes/RouteChangeConfirmation.vue @@ -41,10 +41,8 @@ export default { watch: { nextRoute (newVal, oldVal) { if (newVal !== oldVal && this.$store.getters['submissionNotes/workInProgress']) { - console.log('here') this.dialog = true } else { - console.log('there') this.nextRoute() } } diff --git a/frontend/src/store/modules/authentication.ts b/frontend/src/store/modules/authentication.ts index ce8bbb04..b0101d5d 100644 --- a/frontend/src/store/modules/authentication.ts +++ b/frontend/src/store/modules/authentication.ts @@ -1,9 +1,28 @@ import * as api from '@/api' import gradySays from '../grady_speak' +import {ActionContext, Module} from "vuex"; -function initialState () { +export interface Credentials { + username: string, + password: string +} + +interface AuthState { + token: string, + lastTokenRefreshTry: number, + refreshingToken: boolean, + jwtTimeDelta: number, + message: string, + user: { + pk: string, + username: string, + role: string, + is_admin: boolean + } +} +function initialState (): AuthState { return { - token: sessionStorage.getItem('token'), + token: sessionStorage.getItem('token') || '', lastTokenRefreshTry: Date.now(), refreshingToken: false, jwtTimeDelta: 0, @@ -12,7 +31,7 @@ function initialState () { pk: '', username: '', role: '', - is_admin: '' + is_admin: false } } } @@ -27,53 +46,53 @@ export const authMut = Object.freeze({ SET_REFRESHING_TOKEN: 'SET_REFRESHING_TOKEN' }) -const authentication = { +const authentication: Module<AuthState, any> = { state: initialState(), getters: { gradySpeak: () => { return gradySays[Math.floor(Math.random() * gradySays.length)] }, - isStudent: state => { + isStudent: (state: AuthState) => { return state.user.role === 'Student' }, - isTutor: state => { + isTutor: (state: AuthState)=> { return state.user.role === 'Tutor' }, - isReviewer: state => { + isReviewer: (state: AuthState)=> { return state.user.role === 'Reviewer' }, - isTutorOrReviewer: (state, getters) => { + isTutorOrReviewer: (state: AuthState, getters) => { return getters.isTutor || getters.isReviewer }, - isLoggedIn: state => !!state.token + isLoggedIn: (state: AuthState) => !!state.token }, mutations: { - [authMut.SET_MESSAGE] (state, message) { + [authMut.SET_MESSAGE] (state: AuthState, message: string) { state.message = message }, - [authMut.SET_JWT_TOKEN] (state, token) { + [authMut.SET_JWT_TOKEN] (state: AuthState, token: string) { sessionStorage.setItem('token', token) state.token = token }, - [authMut.SET_JWT_TIME_DELTA] (state, timeDelta) { + [authMut.SET_JWT_TIME_DELTA] (state: AuthState, timeDelta: number) { state.jwtTimeDelta = timeDelta }, - [authMut.SET_USER] (state, user) { + [authMut.SET_USER] (state: AuthState, user) { state.user = user }, - [authMut.SET_REFRESHING_TOKEN] (state, refreshing) { + [authMut.SET_REFRESHING_TOKEN] (state: AuthState, refreshing: boolean) { state.refreshingToken = refreshing }, - [authMut.SET_LAST_TOKEN_REFRESH_TRY] (state) { + [authMut.SET_LAST_TOKEN_REFRESH_TRY] (state: AuthState) { state.lastTokenRefreshTry = Date.now() }, - [authMut.RESET_STATE] (state) { + [authMut.RESET_STATE] (state: AuthState) { sessionStorage.setItem('token', '') Object.assign(state, initialState()) } }, actions: { - async getJWT (context, credentials) { + async getJWT (context: ActionContext<AuthState, any>, credentials: Credentials) { try { const token = await api.fetchJWT(credentials) context.commit(authMut.SET_JWT_TOKEN, token) diff --git a/frontend/src/util/helpers.ts b/frontend/src/util/helpers.ts index a582d317..73e8fc49 100644 --- a/frontend/src/util/helpers.ts +++ b/frontend/src/util/helpers.ts @@ -1,11 +1,12 @@ +import vueInstance from '@/main.ts' -export function nameSpacer (namespace) { - return function (commitType) { +export function nameSpacer (namespace: string) { + return function (commitType: string) { return namespace + commitType } } -export function getObjectValueByPath (obj, path) { +export function getObjectValueByPath (obj: any, path: string): any { // credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621 if (!path || path.constructor !== String) return path = path.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties @@ -22,6 +23,11 @@ export function getObjectValueByPath (obj, path) { return obj } +interface GetSetPair { + get: () => any, + set: (val: object) => void +} + /** * Use this method to generate a computed property accessing the store for a Vue instance. * The get method will return the value at this.$store.state.<path>. @@ -31,17 +37,29 @@ export function getObjectValueByPath (obj, path) { * @param namespace to prepend the mutation type with * @returns {*} */ -export function createComputedGetterSetter ({path, mutation, namespace}) { +export function createComputedGetterSetter ( + {path, mutation, namespace}: + {path: string, mutation: string, namespace:string}): GetSetPair { return { - get () { - return getObjectValueByPath(this.$store.state, path) + get (): any { + return getObjectValueByPath(vueInstance.$store.state, path) }, - set (val) { - this.$store.commit(`${namespace ? namespace + '/' : ''}${mutation}`, val) + set (val: object): void { + vueInstance.$store.commit(`${namespace ? namespace + '/' : ''}${mutation}`, val) } } } +interface StateMapperItem { + name: string, + mutation: string, + path?: string +} + +interface MappedState { + [key: string]: GetSetPair +} + /** * Returns an object of generated computed getter/setter pairs. * Can be used to quickly bind a stores state and corresponding setters to a vue component @@ -49,12 +67,14 @@ export function createComputedGetterSetter ({path, mutation, namespace}) { * @param pathPrefix if set, all items path will be prepended by the path prefix * @param items array that contains objects {name, path, mutation} */ -export function mapStateToComputedGetterSetter ({namespace = '', pathPrefix = '', items = []}) { - return items.reduce((acc, curr) => { +export function mapStateToComputedGetterSetter ( + {namespace = '', pathPrefix = '', items = []}: + {namespace: string, pathPrefix: string, items: StateMapperItem[]}): MappedState { + return items.reduce((acc: MappedState, curr) => { // if no path is give, use name const itemPath = curr.path || curr.name const path = pathPrefix ? `${pathPrefix}.${itemPath}` : itemPath - acc[curr.name] = createComputedGetterSetter({...curr, path, namespace}) + acc[curr.name] = createComputedGetterSetter({mutation: curr.mutation, path, namespace}) return acc }, {}) } @@ -67,13 +87,13 @@ export function cartesian (a, b, ...c) { } // flatten an array -export function flatten (list) { +export function flatten (list: any[]): any[] { return list.reduce( (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [] ) } -export function objectifyArray (arr, key = 'pk') { +export function objectifyArray<T> (arr: T[], key = 'pk'): {[key: string]: T} { return arr.reduce((acc, curr) => { acc[curr[key]] = curr return acc diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c19146b1..c800f3ea 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "esnext", "module": "esnext", - "strict": true, + "strict": false, "jsx": "preserve", "importHelpers": true, "moduleResolution": "node", @@ -16,7 +16,7 @@ ] }, "lib": [ - "es2015", + "es2017", "dom", "dom.iterable", "scripthost" diff --git a/frontend/vue.config.js b/frontend/vue.config.js index 29c09438..d3212539 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -5,7 +5,8 @@ const projectRoot = path.resolve(__dirname) module.exports = { assetsDir: 'static', devServer: { - allowedHosts: ['localhost'] + allowedHosts: ['localhost'], + host: 'localhost' }, configureWebpack: { resolve: { -- GitLab From d0091de4afd88a0b024c557ef03096beca6e9afa Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Mon, 6 Aug 2018 15:31:50 +0200 Subject: [PATCH 13/15] Disabled staging --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3e28a168..74e005ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -104,6 +104,7 @@ pages: .staging_template: &staging_definition stage: staging image: docker:latest + when: manual only: - master before_script: -- GitLab From 068dacc22bbc06435e567e01c6e0f253449c64dc Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Mon, 6 Aug 2018 15:55:52 +0200 Subject: [PATCH 14/15] Reactivated staging --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 74e005ab..3e28a168 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -104,7 +104,6 @@ pages: .staging_template: &staging_definition stage: staging image: docker:latest - when: manual only: - master before_script: -- GitLab From 2015309fba1bab6e2dda749723e9846cdeca6cc8 Mon Sep 17 00:00:00 2001 From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de> Date: Mon, 6 Aug 2018 17:19:00 +0200 Subject: [PATCH 15/15] Fixed a bunch possible bugs... --- frontend/src/api.ts | 18 ++++++--- frontend/src/store/actions.ts | 6 +-- frontend/src/store/modules/authentication.ts | 8 ++-- .../modules/feedback_list/feedback-table.ts | 2 +- .../src/store/modules/submission-notes.ts | 4 +- frontend/src/store/modules/subscriptions.ts | 39 ++++++++++--------- frontend/src/store/mutations.ts | 21 ---------- frontend/src/util/helpers.ts | 11 +++--- 8 files changed, 49 insertions(+), 60 deletions(-) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 029722cd..670854ca 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1,5 +1,5 @@ import axios from 'axios' -import {Credentials} from "@/store/modules/authentication"; +import {Credentials} from '@/store/modules/authentication' function addFieldsToUrl ({url, fields = []}: {url: string, fields?: string[]}) { return fields.length > 0 ? url + '?fields=pk,' + fields : url @@ -68,7 +68,7 @@ export async function fetchAllStudents (fields: string[] = []) { } export async function fetchStudent ({pk, fields = []}: - {pk: string, fields?: string[]}) { +{pk: string, fields?: string[]}) { const url = addFieldsToUrl({ url: `/api/student/${pk}/`, fields @@ -111,7 +111,7 @@ export async function fetchFeedback ({ofSubmission}) { } export async function fetchExamType ({examPk, fields = []}: - {examPk: string, fields?: string[]}) { +{examPk?: string, fields?: string[]}) { const url = addFieldsToUrl({ url: `/api/examtype/${examPk !== undefined ? examPk + '/' : ''}`, fields}) @@ -126,8 +126,16 @@ export async function fetchStatistics (opt = {fields: []}) { return (await ax.get(url)).data } -export async function subscribeTo (type, key, stage) { - let data = { +interface SubscriptionPayload { + /* eslint-disable */ + query_type: string, + query_key?: string, + feedback_stage?: string + /* eslint-enable */ +} + +export async function subscribeTo (type: string, key: string, stage: string) { + let data: SubscriptionPayload = { query_type: type } diff --git a/frontend/src/store/actions.ts b/frontend/src/store/actions.ts index b6ce7879..47ca46fc 100644 --- a/frontend/src/store/actions.ts +++ b/frontend/src/store/actions.ts @@ -53,9 +53,9 @@ const actions = { handleError(err, dispatch, 'Unable to fetch tutor data') } }, - async getAllFeedback ({commit, dispatch}, fields = []) { + async getAllFeedback ({commit, dispatch}, fields: string[] = []) { try { - const feedback = await api.fetchAllFeedback({fields}) + const feedback = await api.fetchAllFeedback(fields) commit(mut.SET_ALL_FEEDBACK, feedback) commit(mut.MAP_FEEDBACK_OF_SUBMISSION_TYPE) } catch (err) { @@ -110,7 +110,7 @@ const actions = { commit('submissionNotes/' + subNotesMut.RESET_STATE) commit(authMut.SET_MESSAGE, message) router.push({name: 'login'}) - router.go() + router.go(0) } } diff --git a/frontend/src/store/modules/authentication.ts b/frontend/src/store/modules/authentication.ts index b0101d5d..9e0c95e7 100644 --- a/frontend/src/store/modules/authentication.ts +++ b/frontend/src/store/modules/authentication.ts @@ -1,6 +1,6 @@ import * as api from '@/api' import gradySays from '../grady_speak' -import {ActionContext, Module} from "vuex"; +import {ActionContext, Module} from 'vuex' export interface Credentials { username: string, @@ -17,7 +17,7 @@ interface AuthState { pk: string, username: string, role: string, - is_admin: boolean + is_admin: boolean //eslint-disable-line } } function initialState (): AuthState { @@ -55,10 +55,10 @@ const authentication: Module<AuthState, any> = { isStudent: (state: AuthState) => { return state.user.role === 'Student' }, - isTutor: (state: AuthState)=> { + isTutor: (state: AuthState) => { return state.user.role === 'Tutor' }, - isReviewer: (state: AuthState)=> { + isReviewer: (state: AuthState) => { return state.user.role === 'Reviewer' }, isTutorOrReviewer: (state: AuthState, getters) => { diff --git a/frontend/src/store/modules/feedback_list/feedback-table.ts b/frontend/src/store/modules/feedback_list/feedback-table.ts index 35e526ac..4ea766a8 100644 --- a/frontend/src/store/modules/feedback_list/feedback-table.ts +++ b/frontend/src/store/modules/feedback_list/feedback-table.ts @@ -41,7 +41,7 @@ const feedbackTable = { actions: { mapFeedbackHistOfSubmissionType ({getters, commit, state}) { for (const feedback of Object.values(state.feedbackHist)) { - const type = getters.getSubmissionType(feedback.of_submission_type) + const type = getters.getSubmissionType((feedback as any).of_submission_type) commit(feedbackTableMut.SET_FEEDBACK_OF_SUBMISSION_TYPE, {feedback, type}) } }, diff --git a/frontend/src/store/modules/submission-notes.ts b/frontend/src/store/modules/submission-notes.ts index 08669ce0..cd02557f 100644 --- a/frontend/src/store/modules/submission-notes.ts +++ b/frontend/src/store/modules/submission-notes.ts @@ -122,7 +122,7 @@ const submissionNotes = { deleteComments: async function ({state}) { return Promise.all( Object.values(state.commentsMarkedForDeletion).map(comment => { - return api.deleteComment(comment) + return api.deleteComment((comment as any)) }) ) }, @@ -143,7 +143,7 @@ const submissionNotes = { if (!state.hasOrigFeedback) { return api.submitFeedbackForAssignment({feedback}) } else { - feedback.pk = state.origFeedback.pk + feedback['pk']= state.origFeedback.pk return api.submitUpdatedFeedback({feedback}) } } diff --git a/frontend/src/store/modules/subscriptions.ts b/frontend/src/store/modules/subscriptions.ts index 531ea809..588867f0 100644 --- a/frontend/src/store/modules/subscriptions.ts +++ b/frontend/src/store/modules/subscriptions.ts @@ -50,10 +50,10 @@ const subscriptions = { return stages }, availableSubmissionTypeQueryKeys (state, getters, rootState) { - return Object.values(rootState.submissionTypes).map(subType => subType.pk) + return Object.values(rootState.submissionTypes).map((subType: any) => subType.pk) }, availableExamTypeQueryKeys (state, getters, rootState) { - return Object.values(rootState.examTypes).map(examType => examType.pk) + return Object.values(rootState.examTypes).map((examType: any) => examType.pk) }, activeSubscription (state) { return state.subscriptions[state.activeSubscriptionPk] @@ -86,25 +86,26 @@ const subscriptions = { acc[curr] = subscriptionsByType() return acc }, {}) - Object.values(state.subscriptions).forEach(subscription => { + Object.values(state.subscriptions).forEach((subscription: any) => { subscriptionsByStage[subscription.feedback_stage][subscription.query_type].push(subscription) }) // sort the resulting arrays in subscriptions lexicographically by their query_keys - const sortSubscriptions = subscriptionsByType => Object.values(subscriptionsByType).forEach(arr => { - if (arr.length > 1 && arr[0].hasOwnProperty('query_key')) { - arr.sort((subA, subB) => { - const subALower = getters.resolveSubscriptionKeyToName(subA).toLowerCase() - const subBLower = getters.resolveSubscriptionKeyToName(subB).toLowerCase() - if (subALower < subBLower) { - return -1 - } else if (subALower > subBLower) { - return 1 - } else { - return 0 - } - }) - } - }) + const sortSubscriptions = subscriptionsByType => Object.values(subscriptionsByType) + .forEach((arr: object[]) => { + if (arr.length > 1 && arr[0].hasOwnProperty('query_key')) { + arr.sort((subA, subB) => { + const subALower = getters.resolveSubscriptionKeyToName(subA).toLowerCase() + const subBLower = getters.resolveSubscriptionKeyToName(subB).toLowerCase() + if (subALower < subBLower) { + return -1 + } else if (subALower > subBLower) { + return 1 + } else { + return 0 + } + }) + } + }) Object.values(subscriptionsByStage).forEach(subscriptionsByType => { sortSubscriptions(subscriptionsByType) }) @@ -186,7 +187,7 @@ const subscriptions = { } }, async cleanAssignmentsFromSubscriptions ({commit, state, dispatch}, excludeActive = true) { - Object.values(state.subscriptions).forEach(subscription => { + Object.values(state.subscriptions).forEach((subscription: any) => { if (!excludeActive || subscription.pk !== state.activeSubscriptionPk) { subscription.assignments.forEach(assignment => { api.deleteAssignment({assignment}) diff --git a/frontend/src/store/mutations.ts b/frontend/src/store/mutations.ts index 2e4c4081..1c9e80ad 100644 --- a/frontend/src/store/mutations.ts +++ b/frontend/src/store/mutations.ts @@ -26,27 +26,6 @@ const mutations = { return acc }, {}) }, - [mut.SET_SUBSCRIPTIONS] (state, subscriptions) { - state.subscriptions = subscriptions.reduce((acc, curr) => { - acc[curr['pk']] = curr - return acc - }, {}) - for (let subscription of Object.values(subscriptions)) { - for (let assignment of subscription.assignments) { - if (assignment.feedback) { - Vue.set(assignment.feedback, 'feedback_stage_for_user', subscription.feedback_stage) - } - } - } - }, - [mut.SET_SUBSCRIPTION] (state, subscription) { - Vue.set(state.subscriptions, subscription.pk, subscription) - for (let assignment of subscription.assignments) { - if (assignment.feedback) { - Vue.set(assignment.feedback, 'feedback_stage_for_user', subscription.feedback_stage) - } - } - }, [mut.SET_STUDENTS] (state, students) { state.students = students.reduce((acc, curr) => { acc[curr.pk] = mapStudent(curr, state.studentMap) diff --git a/frontend/src/util/helpers.ts b/frontend/src/util/helpers.ts index 73e8fc49..1927f50a 100644 --- a/frontend/src/util/helpers.ts +++ b/frontend/src/util/helpers.ts @@ -38,8 +38,8 @@ interface GetSetPair { * @returns {*} */ export function createComputedGetterSetter ( - {path, mutation, namespace}: - {path: string, mutation: string, namespace:string}): GetSetPair { + {path, mutation, namespace}: + {path: string, mutation: string, namespace:string}): GetSetPair { return { get (): any { return getObjectValueByPath(vueInstance.$store.state, path) @@ -68,8 +68,8 @@ interface MappedState { * @param items array that contains objects {name, path, mutation} */ export function mapStateToComputedGetterSetter ( - {namespace = '', pathPrefix = '', items = []}: - {namespace: string, pathPrefix: string, items: StateMapperItem[]}): MappedState { + {namespace = '', pathPrefix = '', items = []}: + {namespace: string, pathPrefix: string, items: StateMapperItem[]}): MappedState { return items.reduce((acc: MappedState, curr) => { // if no path is give, use name const itemPath = curr.path || curr.name @@ -83,6 +83,7 @@ export function mapStateToComputedGetterSetter ( // https://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript/43053803#43053803 let cartesianHelper = (a, b) => [].concat(...a.map(a => b.map(b => [].concat(a, b)))) export function cartesian (a, b, ...c) { + // @ts-ignore can be ignored since cartesian is only recursively called if b si truthy return b ? cartesian(cartesianHelper(a, b), ...c) : a } @@ -100,7 +101,7 @@ export function objectifyArray<T> (arr: T[], key = 'pk'): {[key: string]: T} { }, {}) } -export function once (fn, context) { +export function once (fn: Function, context?: object) { let result return function () { if (!result) { -- GitLab