From f39d2a2af6a715c4725d836b2c7d754a0977516a Mon Sep 17 00:00:00 2001
From: "robinwilliam.hundt" <robinwilliam.hundt@stud.uni-goettingen.de>
Date: Fri, 28 Sep 2018 13:29:31 +0200
Subject: [PATCH] Type safe FeedbackTable components

---
 .../feedback_list/FeedbackListHelpCard.vue    |   8 +-
 .../feedback_list/FeedbackSearchOptions.vue   |  97 ++++-----
 .../feedback_list/FeedbackTable.vue           | 199 +++++++++---------
 .../feedback_list/feedback-search-options.ts  |   5 +-
 4 files changed, 153 insertions(+), 156 deletions(-)

diff --git a/frontend/src/components/feedback_list/FeedbackListHelpCard.vue b/frontend/src/components/feedback_list/FeedbackListHelpCard.vue
index 0bfd4737..6302a0d8 100644
--- a/frontend/src/components/feedback_list/FeedbackListHelpCard.vue
+++ b/frontend/src/components/feedback_list/FeedbackListHelpCard.vue
@@ -18,9 +18,11 @@
   </v-layout>
 </template>
 
-<script>
-export default {
-  name: 'feedback-list-help-card'
+<script lang="ts">
+import {Vue, Component} from 'vue-property-decorator'
+
+@Component
+export default class FeedbackListHelpCard extends Vue {
 }
 </script>
 
diff --git a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue
index 7996bbdc..439e9bae 100644
--- a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue
+++ b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue
@@ -60,66 +60,57 @@
   </v-layout>
 </template>
 
-<script>
+<script lang="ts">
+import {Vue, Component, Prop} from 'vue-property-decorator'
 import {mapState, mapGetters} from 'vuex'
-import {FeedbackSearchOptions} from '@/store/modules/feedback_list/feedback-search-options'
+import {FeedbackSearchOptions as SearchOptions} from '@/store/modules/feedback_list/feedback-search-options'
 import {mapStateToComputedGetterSetter} from '@/util/helpers'
 import {Authentication} from '@/store/modules/authentication'
 import { actions } from '@/store/actions'
+import { getters } from '@/store/getters';
 
-export default {
-  name: 'feedback-search-options',
-  data () {
-    return {
-      feedbackStages: ['Initial feedback', 'Validation']
-    }
-  },
-  computed: {
-    ...mapState([
-      'tutors'
-    ]),
-    isReviewer () { return Authentication.isReviewer },
-    tutorNames () {
-      return this.tutors.map(tutor => tutor.username)
-    },
-    ...mapStateToComputedGetterSetter({
-      pathPrefix: 'FeedbackSearchOptions',
-      items: [
-        {
-          name: 'showFinal',
-          mutation: FeedbackSearchOptions.SET_SHOW_FINAL
-        },
-        {
-          name: 'searchOtherUserComments',
-          mutation: FeedbackSearchOptions.SET_SEARCH_OTHER_USER_COMMENTS
-        },
-        {
-          name: 'caseSensitive',
-          mutation: FeedbackSearchOptions.SET_CASE_SENSITIVE
-        },
-        {
-          name: 'useRegex',
-          mutation: FeedbackSearchOptions.SET_USE_REGEX
-        },
-        {
-          name: 'filterByTutors',
-          mutation: FeedbackSearchOptions.SET_FILTER_BY_TUTORS
-        },
-        {
-          name: 'filterByStage',
-          mutation: FeedbackSearchOptions.SET_FILTER_BY_STAGE
-        }
+@Component
+export default class FeedbackSearchOptions extends Vue {
+  feedbackStages = ['Initial feedback', 'Validation']
+
+  get tutors () { return getters.state.tutors }
+
+  get isReviewer () { return Authentication.isReviewer }
+  get tutorNames () {
+    return this.tutors.map(tutor => tutor.username)
+  }
 
-      ]
-    })
-  },
-  methods: {
-    loadTutors () {
-      if (this.tutors.length === 0 && this.isReviewer) {
-        actions.getTutors()
-      }
+  get showFinal () { return SearchOptions.state.showFinal}
+  get searchOtherUserComments () { return SearchOptions.state.searchOtherUserComments}
+  get caseSensitive () { return SearchOptions.state.caseSensitive}
+  get useRegex () { return SearchOptions.state.useRegex}
+  get filterByTutors () { return SearchOptions.state.filterByTutors}
+  get filterByStage () { return SearchOptions.state.filterByStage}
+
+  set showFinal (val) {
+    SearchOptions.SET_SHOW_FINAL(val)
+  }
+  set searchOtherUserComments (val) {
+    SearchOptions.SET_SEARCH_OTHER_USER_COMMENTS(val)
+  }
+  set caseSensitive (val) {
+    SearchOptions.SET_CASE_SENSITIVE(val)
+  }
+  set useRegex (val) {
+    SearchOptions.SET_USE_REGEX(val)
+  }
+  set filterByTutors (val) {
+    SearchOptions.SET_FILTER_BY_TUTORS(val)
+  }
+  set filterByStage (val) {
+    SearchOptions.SET_FILTER_BY_STAGE(val)
+  }
+
+  loadTutors () {
+    if (this.tutors.length === 0 && this.isReviewer) {
+      actions.getTutors()
     }
-  },
+  }
   created () {
     this.loadTutors()
   }
diff --git a/frontend/src/components/feedback_list/FeedbackTable.vue b/frontend/src/components/feedback_list/FeedbackTable.vue
index 1925c5f9..445173ef 100644
--- a/frontend/src/components/feedback_list/FeedbackTable.vue
+++ b/frontend/src/components/feedback_list/FeedbackTable.vue
@@ -37,117 +37,120 @@
   </v-card>
 </template>
 
-<script>
+<script lang="ts">
 import {mapState, mapGetters} from 'vuex'
+import {Component, Prop, Vue} from 'vue-property-decorator'
 import {getObjectValueByPath} from '@/util/helpers'
-import FeedbackSearchOptions from '@/components/feedback_list/FeedbackSearchOptions'
+import FeedbackSearchOptions from '@/components/feedback_list/FeedbackSearchOptions.vue'
 import { FeedbackSearchOptions as OptionsModule } from '@/store/modules/feedback_list/feedback-search-options'
-import { FeedbackTable } from '@/store/modules/feedback_list/feedback-table'
+import { FeedbackTable as FeedbackModule, FeedbackHistoryItem} from '@/store/modules/feedback_list/feedback-table'
+import { Subscription, Feedback } from '@/models';
+import { actions } from '@/store/actions';
+import { getters } from '@/store/getters';
 
-export default {
-  computed: {
-    showFinal () { return OptionsModule.state.showFinal },
-    searchOtherUserComments () { return OptionsModule.state.searchOtherUserComments },
-    caseSensitive () { return OptionsModule.state.caseSensitive },
-    useRegex () { return OptionsModule.state.useRegex },
-    filterByTutors () { return OptionsModule.state.filterByTutors },
-    filterByStage () { return OptionsModule.state.filterByStage },
+@Component({
+  components: {
+    FeedbackSearchOptions
+  }
+})
+export default class FeedbackTable extends Vue {
+  get showFinal () { return OptionsModule.state.showFinal }
+  get searchOtherUserComments () { return OptionsModule.state.searchOtherUserComments }
+  get caseSensitive () { return OptionsModule.state.caseSensitive }
+  get useRegex () { return OptionsModule.state.useRegex }
+  get filterByTutors () { return OptionsModule.state.filterByTutors }
+  get filterByStage () { return OptionsModule.state.filterByStage }
 
-    stageFilterString () { return OptionsModule.stageFilterString },
+  get stageFilterString () { return OptionsModule.stageFilterString }
 
-    feedback () {
-      return Object.values(FeedbackTable.state.feedbackHist).filter(feedback => {
-        return this.checkFinal(feedback) && this.filterFeedbackByTutorStage(feedback)
-      })
-    }
-  },
-  components: {FeedbackSearchOptions},
-  name: 'feedback-table',
-  data () {
-    return {
-      search: '',
-      prefetchWhenLessItems: 11,
-      headers: [
-        {
-          text: 'Type',
-          align: 'left',
-          value: 'ofSubmissionType'
-        },
-        {
-          text: 'score',
-          value: 'score'
-        },
-        {
-          text: 'Created',
-          value: 'created'
-        },
-        {
-          text: 'Final',
-          value: 'final'
-        }
-      ]
-    }
-  },
-  methods: {
-    filterFeedbackByTutorStage (feedback) {
-      if (feedback.hasOwnProperty('history')) {
-        const associatedTutors = this.stageFilterString === 'all'
-          ? Object.values(feedback.history).map(histEntry => histEntry.owner)
-          : feedback.history[this.stageFilterString] ? [feedback.history[this.stageFilterString].owner] : []
-        return this.filterByTutors.length === 0 ||
-            associatedTutors.some(tutor => this.filterByTutors.includes(tutor))
-      }
-      // if feedback is not filtered by tutor or stage return true, otherwise false
-      return this.filterByTutors.length === 0
+  get feedback () {
+    return Object.values(FeedbackModule.state.feedbackHist).filter(feedback => {
+      return this.checkFinal(feedback) && this.filterFeedbackByTutorStage(feedback)
+    })
+  }
+
+  search = ''
+  prefetchWhenLessItems = 11
+  headers = [
+    {
+      text: 'Type',
+      align: 'left',
+      value: 'ofSubmissionType'
     },
-    showSubmission (submissionPk) {
-      this.$router.push(`/feedback/${submissionPk}`)
+    {
+      text: 'score',
+      value: 'score'
     },
-    prefetchSubmission (submissionPk) {
-      this.$store.dispatch('getSubmissionFeedbackTest', {pk: submissionPk})
+    {
+      text: 'Created',
+      value: 'created'
     },
-    prefetchFilteredItems (items) {
-      if (items.length < this.prefetchWhenLessItems) {
-        for (let item of items) {
-          if (!this.$store.state.submissions[item.ofSubmission]) {
-            this.prefetchSubmission(item.ofSubmission)
-          }
+    {
+      text: 'Final',
+      value: 'final'
+    }
+  ]
+
+  filterFeedbackByTutorStage (feedback: FeedbackHistoryItem) {
+    if (!feedback.history) {
+      // if feedback is not filtered by tutor or stage return true, otherwise false
+      return this.filterByTutors.length === 0
+    }
+    const associatedTutors = this.stageFilterString === 'all'
+      ? Object.values(feedback.history).filter(histEntry => !!histEntry).map(histEntry => histEntry!.owner)
+      : feedback.history[<Subscription.FeedbackStageEnum> this.stageFilterString] ?
+      [feedback.history[<Subscription.FeedbackStageEnum> this.stageFilterString]!.owner] : []
+    return this.filterByTutors.length === 0 ||
+        associatedTutors.some(tutor => this.filterByTutors.includes(tutor))
+
+  }
+  showSubmission (submissionPk: string) {
+    this.$router.push(`/feedback/${submissionPk}`)
+  }
+  prefetchSubmission (submissionPk: string) {
+    actions.getSubmissionFeedbackTest({pk: submissionPk})
+  }
+  prefetchFilteredItems (items: Feedback[]) {
+    if (items.length < this.prefetchWhenLessItems) {
+      for (let item of items) {
+        if (item.ofSubmission && !getters.state.submissions[item.ofSubmission]) {
+          this.prefetchSubmission(item.ofSubmission)
         }
       }
-    },
-    containsSearch (val, search) {
-      if (!val || typeof val === 'boolean') {
-        return false
-      }
-      if (search instanceof RegExp) {
-        return search.test(val.toString())
-      }
-      if (this.caseSensitive) {
-        return val.toString().indexOf(search) !== -1
-      } else {
-        return val.toString().toLowerCase().indexOf(search.toLowerCase()) !== -1
-      }
-    },
-    checkFinal (feedback) {
-      return this.showFinal ? true : !feedback.isFinal
-    },
-    commentFilter (feedback, search, filter) {
-      return Object.values(feedback.feedbackLines).some(line => line.some(comment => {
-        return filter(comment.text, search)
-      }))
-    },
-    searchFunction (items, search, filter) {
-      if (search.trim() === '') return items
-      const props = this.headers.map(h => h.value)
-      search = this.useRegex ? new RegExp(search, this.caseSensitive ? 'u' : 'iu') : search
-      let filteredItems = items.filter(feedback => {
-        return props.some(prop => filter(getObjectValueByPath(feedback, prop), search) ||
-            this.commentFilter(feedback, search, filter))
-      })
-      this.prefetchFilteredItems(filteredItems)
-      return filteredItems
     }
   }
+  containsSearch (val: {toString: () => string}, search: string | RegExp) {
+    if (!val || typeof val === 'boolean') {
+      return false
+    }
+    if (search instanceof RegExp) {
+      return search.test(val.toString())
+    }
+    if (this.caseSensitive) {
+      return val.toString().indexOf(search) !== -1
+    } else {
+      return val.toString().toLowerCase().indexOf(search.toLowerCase()) !== -1
+    }
+  }
+  checkFinal (feedback: Feedback) {
+    return this.showFinal ? true : !feedback.isFinal
+  }
+  commentFilter (feedback: Feedback, search: string | RegExp, filter: any) {
+    return Object.values(feedback.feedbackLines || {}).some(line => line.some(comment => {
+      return filter(comment.text, search)
+    }))
+  }
+  searchFunction (items: Feedback[], search: string, filter: any) {
+    if (search.trim() === '') return items
+    const props = this.headers.map(h => h.value)
+    const searchVal = this.useRegex ? new RegExp(search, this.caseSensitive ? 'u' : 'iu') : search
+    let filteredItems = items.filter(feedback => {
+      return props.some(prop => filter(getObjectValueByPath(feedback, prop), searchVal) ||
+          this.commentFilter(feedback, searchVal, filter))
+    })
+    this.prefetchFilteredItems(filteredItems)
+    return filteredItems
+  }
 }
 </script>
 
diff --git a/frontend/src/store/modules/feedback_list/feedback-search-options.ts b/frontend/src/store/modules/feedback_list/feedback-search-options.ts
index 6a69437c..b93895a5 100644
--- a/frontend/src/store/modules/feedback_list/feedback-search-options.ts
+++ b/frontend/src/store/modules/feedback_list/feedback-search-options.ts
@@ -1,6 +1,7 @@
 import {Module} from 'vuex'
 import {RootState} from '@/store/store'
 import { getStoreBuilder } from 'vuex-typex'
+import { Subscription } from '@/models';
 
 export const namespace = 'feedbackSearchOptions'
 
@@ -27,8 +28,8 @@ function initialState (): FeedbackSearchOptionsState {
 const mb = getStoreBuilder<RootState>().module('FeedbackSearchOptions', initialState())
 
 const mapStageDisplayToApiString: {[index: string]: string} = {
-  'Initial feedback': 'feedback-creation',
-  'Validation': 'feedback-validation'
+  'Initial feedback': Subscription.FeedbackStageEnum.Creation,
+  'Validation': Subscription.FeedbackStageEnum.Validation
 }
 
 const stateGetter = mb.state()
-- 
GitLab