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 (2)
Showing
with 127 additions and 73 deletions
......@@ -36,6 +36,8 @@ build_test_env:
build_frontend:
image: node:carbon
stage: build
variables:
VUE_APP_CI: 'true'
script:
- cd frontend
- yarn
......
......@@ -191,7 +191,7 @@ class VisibleCommentFeedbackSerializer(FeedbackSerializer):
serializer = FeedbackCommentSerializer(
comments,
many=True,
fields=('pk', 'text', 'created', 'modified', 'of_line',)
fields=('pk', 'text', 'created', 'modified', 'of_line', 'labels')
)
# this is a weird hack because, for some reason, serializer.data
# just won't contain the correct data. Instead .data returns a list
......@@ -203,4 +203,4 @@ class VisibleCommentFeedbackSerializer(FeedbackSerializer):
class Meta:
model = Feedback
fields = ('pk', 'of_submission', 'is_final', 'score', 'feedback_lines',
'created', 'of_submission_type')
'created', 'of_submission_type', 'labels')
<!DOCTYPE html>
<html>
<head>
<title>Grady Frontend placeholder</title>
</head>
<body>
This will be replaced in production.
</body>
</html>
{% load static %}
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><title>Grady</title><link href={% static 'css/app.bb1d5faa.css' %} rel=preload as=style><link href={% static 'css/chunk-vendors.23c885e7.css' %} rel=preload as=style><link href={% static 'js/app.b896b8ee.js' %} rel=preload as=script><link href={% static 'js/chunk-vendors.5a1b8f1d.js' %} rel=preload as=script><link href={% static 'css/chunk-vendors.23c885e7.css' %} rel=stylesheet><link href={% static 'css/app.bb1d5faa.css' %} rel=stylesheet></head><body><noscript><strong>We're sorry but frontend doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src={% static 'js/chunk-vendors.5a1b8f1d.js' %}></script><script src={% static 'js/app.b896b8ee.js' %}></script></body></html>
\ No newline at end of file
......@@ -23,10 +23,10 @@ class LabelsTestCases(APITestCase):
}
cls.label_url = '/api/label/'
def test_student_can_not_read_labels(self):
def test_student_can_read_labels(self):
self.client.force_authenticate(user=self.student)
response = self.client.get(self.label_url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(FeedbackLabel.objects.count(), 0)
def test_student_can_not_write_labels(self):
......
......@@ -3,6 +3,7 @@ import logging
from django.db.models import Case, When, IntegerField, Sum, Q
from rest_framework import mixins, viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from core import models, permissions, serializers
......@@ -19,6 +20,12 @@ class LabelApiViewSet(viewsets.GenericViewSet,
queryset = models.FeedbackLabel.objects.all()
serializer_class = serializers.LabelSerializer
def get_permissions(self):
if self.action == 'list':
return [IsAuthenticated(), ]
else:
return super().get_permissions()
class LabelStatistics(viewsets.ViewSet):
......
......@@ -17,7 +17,7 @@ import {
} from '@/models'
function getInstanceBaseUrl (): string {
if (process.env.NODE_ENV === 'production') {
if (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test') {
return `${window.location.protocol}//${window.location.host}${window.location.pathname}`.replace(/\/+$/, '')
} else {
return 'http://localhost:8000/'
......@@ -230,8 +230,8 @@ export async function changeActiveForUser (userPk: string, active: boolean): Pro
return (await ax.patch(`/api/user/${userPk}/change_active/`, { 'is_active': active })).data
}
export async function getLabels () {
return (await ax.get('/api/label')).data
export async function getLabels (): Promise<FeedbackLabel[]> {
return (await ax.get('/api/label/')).data
}
export async function createLabel (payload: Partial<FeedbackLabel>) {
......
......@@ -11,20 +11,18 @@
<v-card-title v-else>
No Tests available
</v-card-title>
<v-expansion-panel v-if="expanded">
<v-expansion-panel-content v-for="item in tests" :key="item.pk" >
<div slot="header" name="test-name-label">
<v-layout row class="pr-4" >
{{item.name}}
<v-card-text v-if="expanded">
<v-flex sm12 v-for="item in tests" :key="item.pk">
<div name="test-name-label">
<v-layout row class="pr-4">
<h3>{{item.name}}</h3>
<v-spacer/>
{{item.label}}
<h3>{{item.label}}</h3>
</v-layout>
</div>
<v-card name="test-output">
<v-card-text class="test-output">{{item.annotation}}</v-card-text>
</v-card>
</v-expansion-panel-content>
</v-expansion-panel>
<span class="test-output">{{item.annotation}}</span>
</v-flex>
</v-card-text>
</v-card>
</template>
......@@ -43,7 +41,8 @@ export default {
},
data () {
return {
expanded: this.expand
expanded: this.expand,
panels: this.tests.map(_ => true)
}
}
}
......
......@@ -51,15 +51,15 @@ export default class FeedbackLabelsList extends Vue {
return !this.sidebar || (this.sidebar && !UI.state.sideBarCollapsed)
}
async created() {
await this.refreshLabels()
created() {
this.refreshLabels()
}
async refreshLabels() {
refreshLabels() {
this.updating = true
const labels = await getLabels()
FeedbackLabels.SET_LABELS(labels)
this.updating = false
FeedbackLabels.getLabels().finally(() => {
this.updating = false
})
}
}
</script>
......
......@@ -17,12 +17,12 @@ export default class commentLabelSelector extends Vue {
/**
* Returns array of label pk's where feedbackType is
* either "origFeedback" or "updatedFeedback"
*
*
* Will return null when labels property does not exist on the requested state's comment
* This is the case when the labels have not been updated, as we don't want to have
* the labels field in the object if the labels have not changed.
*/
copyStateLabels(feedbackType: FeedbackType): number[] | null {
copyStateLabels(feedbackType: FeedbackType): number[] | null {
const currentLine = this.getFeedbackLine(feedbackType)
if (currentLine && currentLine.labels) {
return currentLine.labels
......@@ -62,7 +62,6 @@ export default class commentLabelSelector extends Vue {
if (labelsOrig === null || labelsOrig.length === 0) {
return new Array ()
}
const removedLabels = this.getRemovedLabels()
const addedLabels = this.getAddedLabels()
......@@ -114,7 +113,7 @@ export default class commentLabelSelector extends Vue {
})
if (!label) return undefined
return {
return {
pk: val,
name: label.name,
description: label.description,
......@@ -131,4 +130,4 @@ export default class commentLabelSelector extends Vue {
return mappedLabels;
}
}
\ No newline at end of file
}
......@@ -32,7 +32,7 @@
<v-icon v-else size="20px">restore</v-icon>
</v-btn>
</div>
<v-layout v-if="showLabels" ml-2>
<v-layout v-if="showLabels && correctorView" ml-2>
<v-flex sm4>
<v-flex sm12>
UNCHANGED
......@@ -70,6 +70,18 @@
/>
</v-flex>
</v-layout>
<template row wrap v-if="!correctorView" align-center>
<v-layout row wrap align-center v-for="label in unchangedLabels" :key="label.pk">
<v-flex sm6>
<feedback-label
v-bind="label"
/>
</v-flex>
<v-flex sm6>
<span><b>Description: </b>{{label.description}}</span>
</v-flex>
</v-layout>
</template>
</div>
</template>
......@@ -121,12 +133,16 @@ export default {
showVisibilityIcon: {
type: Boolean,
default: true
},
correctorView: {
type: Boolean,
default: true
}
},
computed: {
commentDisplayable () { return this.text !== "" },
showLabels () {
return this.visibleToStudent &&
showLabels () {
return this.visibleToStudent &&
(this.getUnchangedLabels().length > 0 ||
this.getAddedLabels().length > 0 ||
this.getRemovedLabels().length > 0)
......
......@@ -87,7 +87,7 @@ export default class SubmissionType extends Vue {
}) reverse!: boolean
@Prop({
type: Object,
default: {},
default: () => {return {}},
}) solutionComments!: {[ofLine: number]: SolutionComment[]}
@Prop({
type: Object,
......
......@@ -118,7 +118,6 @@ export default class SolutionComment extends Vue {
}
toggleEditing() {
console.log('adasd')
this.editing = !this.editing
this.editedText = this.text
}
......
......@@ -17,19 +17,26 @@ import 'highlight.js/styles/atom-one-light.css'
import "@/util/shortkeys"
Vue.use(Vuetify)
Vue.use(Clipboard)
Vue.use(Notifications)
const integrations = [
new Integrations.Vue({Vue, attachProps: true, logErrors: true}),
new Integrations.ExtraErrorData({depth: 10}),
new Integrations.CaptureConsole({levels: ['warn', 'error']}),
new Integrations.Debug()
]
if (process.env.NODE_ENV === 'test') {
if (process.env.NODE_ENV === 'test' || process.env.VUE_APP_CI === 'true') {
Sentry.init({
dsn: 'https://c86a983153da412b8aec8b8df9ce51ba@robin-in.space/2',
integrations: [new Integrations.Vue({Vue, attachProps: true, logErrors: true})],
integrations
})
} else if (process.env.NODE_ENV == 'production') {
} else if (process.env.NODE_ENV === 'production') {
Sentry.init({
dsn: 'https://874b896335564d8c9c49137391f8e3f1@robin-in.space/3',
integrations: [new Integrations.Vue({Vue, attachProps: true, logErrors: true})],
integrations
})
}
......
......@@ -925,4 +925,6 @@ export interface VisibleCommentFeedback {
* @memberof VisibleCommentFeedback
*/
ofSubmissionType?: string
labels: number[]
}
......@@ -14,6 +14,7 @@
<script>
import SubmissionList from '@/components/student/SubmissionList.vue'
import { StudentPage } from '@/store/modules/student-page'
import { FeedbackLabels } from '@/store/modules/feedback-labels'
export default {
components: {
......@@ -23,6 +24,7 @@ export default {
created: function () {
if (!this.loaded) {
StudentPage.getStudentData().then(() => {
FeedbackLabels.getLabels()
StudentPage.getStudentSubmissions()
})
}
......
......@@ -25,11 +25,12 @@
<template v-if="feedback">
<template v-for="(comment, index) in feedback.feedbackLines[lineNo]">
<feedback-comment
v-if="feedback.feedbackLines[lineNo] && showFeedback"
v-if="showFeedback"
v-bind="comment"
:line-no="lineNo"
:key="index"
:show_visibility_icon="false"
:key="comment.pk + index"
:showVisibilityIcon="false"
:correctorView="false"
/>
</template>
</template>
......@@ -37,6 +38,21 @@
</tr>
</template>
</base-annotated-submission>
<v-card>
<v-card-title>Labels:</v-card-title>
<v-card-text>
<v-layout row wrap align-center v-for="label in mappedLabels" :key="'global' + label.pk">
<v-flex sm6>
<feedback-label
v-bind="label"
/>
</v-flex>
<v-flex sm6>
<span><b>Description: </b>{{label.description}}</span>
</v-flex>
</v-layout>
</v-card-text>
</v-card>
<submission-tests
:tests="tests"
:expand="true"
......@@ -64,6 +80,9 @@ import { SubmissionNotes } from '@/store/modules/submission-notes'
import ToggleFeedbackVisibilityButton from '@/components/submission_notes/toolbars/ToggleFeedbackVisibilityButton'
import SubmissionTests from '@/components/SubmissionTests'
import NonFinalFeedbackAlert from '@/components/student/NonFinalFeedbackAlert'
import { FeedbackLabels } from '../../store/modules/feedback-labels'
import FeedbackLabel from "@/components/feedback_labels/FeedbackLabel.vue"
export default {
name: 'student-submission-page',
......@@ -75,6 +94,7 @@ export default {
SubmissionLine,
BaseAnnotatedSubmission,
AnnotatedSubmission,
FeedbackLabel,
SubmissionType },
computed: {
id: function () {
......@@ -84,7 +104,14 @@ export default {
tests () { return SubmissionNotes.state.submission.tests },
showFeedback: function (state) { return SubmissionNotes.state.ui.showFeedback },
submissionType () { return StudentPage.state.submissionData[this.id].type },
feedback () { return StudentPage.state.submissionData[this.$route.params.id].feedback }
feedback () { return StudentPage.state.submissionData[this.$route.params.id].feedback },
mappedLabels () {
return this.feedback.labels.map(entry => {
return FeedbackLabels.state.labels.find(label => {
return label.pk === entry
})
})
}
},
methods: {
onRouteMountOrUpdate (routeId) {
......
import { FeedbackLabel } from '@/models';
import { getStoreBuilder } from 'vuex-typex';
import { RootState } from '../store';
import * as api from '@/api'
import Vue from 'vue';
export interface FeedbackLabelsState {
......@@ -40,6 +41,11 @@ function UPDATE_LABEL(state: FeedbackLabelsState, label: FeedbackLabel) {
ADD_LABEL(state, label)
}
async function getLabels() {
const labels = await api.getLabels()
FeedbackLabels.SET_LABELS(labels)
}
export const FeedbackLabels = {
get state() { return stateGetter() },
get availableLabels() { return availableLabelsGetter() },
......@@ -48,5 +54,7 @@ export const FeedbackLabels = {
ADD_LABEL: mb.commit(ADD_LABEL),
REMOVE_LABEL: mb.commit(REMOVE_LABEL),
UPDATE_LABEL: mb.commit(UPDATE_LABEL),
getLabels: mb.dispatch(getLabels)
}
......@@ -60,24 +60,16 @@ function RESET_STATE (state: StudentPageState) {
}
async function getStudentData () {
try {
const studentData = await fetchStudentSelfData()
StudentPage.SET_STUDENT_NAME(studentData.name || '')
StudentPage.SET_EXAM(studentData.exam)
StudentPage.SET_SUBMISSIONS_FOR_LIST(studentData.submissions)
StudentPage.SET_LOADED(true)
} catch (e) {
console.log(e)
}
}
async function getStudentSubmissions () {
try {
const submissions = await fetchStudentSubmissions()
StudentPage.SET_FULL_SUBMISSION_DATA(submissions)
} catch (e) {
console.log(e)
}
}
export const StudentPage = {
......
......@@ -92,6 +92,9 @@ const isFeedbackCreationGetter = mb.read(function isFeedbackCreation(state) {
function SET_SUBMISSION(state: SubmissionNotesState, submission: SubmissionNoType) {
state.submission = submission
if (submission.feedback !== undefined) {
SET_ORIG_FEEDBACK(state, submission.feedback)
}
}
function SET_ORIG_FEEDBACK(state: SubmissionNotesState, feedback: Feedback) {
if (feedback) {
......
import instance from '@/main'
import { parseErrorNotification, parseBlacklist } from '@/util/helpers'
import * as Sentry from '@sentry/browser'
const errorUrlBlacklist = [
"/api/get-token/",
......@@ -7,18 +9,14 @@ const errorUrlBlacklist = [
const blackListRegExp = new RegExp(parseBlacklist(errorUrlBlacklist), "g")
export function errorInterceptor (error: any): any {
// TODO: log errors and store them somewhere
if (error.response.request.responseURL.match(blackListRegExp)) {
return
if (!error.response.request.responseURL.match(blackListRegExp)) {
instance.$notify({
title: 'Request failed.',
text: parseErrorNotification(error.response),
type: 'error',
duration: -1
})
}
instance.$notify({
title: 'Request failed.',
text: parseErrorNotification(error.response),
type: 'error',
duration: -1
})
Sentry.captureException(error)
return Promise.reject(error)
}
\ No newline at end of file
}