fix: test case with no input issue
This commit is contained in:
@@ -352,7 +352,7 @@ const addProgrammingExercises = () => {
|
|||||||
activeFor: [
|
activeFor: [
|
||||||
'ProgrammingExercises',
|
'ProgrammingExercises',
|
||||||
'ProgrammingExerciseForm',
|
'ProgrammingExerciseForm',
|
||||||
'ProgrammingExerciseSubmissionList',
|
'ProgrammingExerciseSubmissions',
|
||||||
'ProgrammingExerciseSubmission',
|
'ProgrammingExerciseSubmission',
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
<template #default="{ column, item }">
|
<template #default="{ column, item }">
|
||||||
<ListRowItem :item="row[column.key]" :align="column.align">
|
<ListRowItem :item="row[column.key]" :align="column.align">
|
||||||
<div v-if="column.key == 'assessment_type'">
|
<div v-if="column.key == 'assessment_type'">
|
||||||
{{ row[column.key] == 'LMS Quiz' ? 'Quiz' : 'Assignment' }}
|
{{ getAssessmentTypeLabel(row[column.key]) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="column.key == 'title'">
|
<div v-else-if="column.key == 'title'">
|
||||||
{{ row[column.key] }}
|
{{ row[column.key] }}
|
||||||
@@ -172,6 +172,24 @@ const getRowRoute = (row) => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (row.assessment_type == 'LMS Programming Exercise') {
|
||||||
|
if (row.submission) {
|
||||||
|
return {
|
||||||
|
name: 'ProgrammingExerciseSubmission',
|
||||||
|
params: {
|
||||||
|
exerciseID: row.assessment_name,
|
||||||
|
submissionID: row.submission.name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
name: 'ProgrammingExerciseSubmission',
|
||||||
|
params: {
|
||||||
|
exerciseID: row.assessment_name,
|
||||||
|
submissionID: 'new',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
name: 'QuizPage',
|
name: 'QuizPage',
|
||||||
@@ -221,4 +239,14 @@ const getStatusTheme = (status) => {
|
|||||||
return 'red'
|
return 'red'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getAssessmentTypeLabel = (type) => {
|
||||||
|
if (type == 'LMS Assignment') {
|
||||||
|
return __('Assignment')
|
||||||
|
} else if (type == 'LMS Quiz') {
|
||||||
|
return __('Quiz')
|
||||||
|
} else if (type == 'LMS Programming Exercise') {
|
||||||
|
return __('Programming Exercise')
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="overflow-x-auto border rounded-md">
|
<div class="overflow-x-auto border rounded-md">
|
||||||
<!-- Header Row -->
|
|
||||||
<div
|
<div
|
||||||
class="grid items-center space-x-4 p-2 border-b"
|
class="grid items-center space-x-4 p-2 border-b"
|
||||||
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
|
:style="{ gridTemplateColumns: getGridTemplateColumns() }"
|
||||||
@@ -15,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Data Rows -->
|
|
||||||
<div
|
<div
|
||||||
v-for="(row, rowIndex) in rows"
|
v-for="(row, rowIndex) in rows"
|
||||||
:key="rowIndex"
|
:key="rowIndex"
|
||||||
@@ -31,18 +30,20 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="relative" ref="menuRef">
|
<div class="relative" ref="menuRef">
|
||||||
<Button variant="ghost">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
@click="(event: MouseEvent) => toggleMenu(rowIndex, event)"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Ellipsis
|
<Ellipsis
|
||||||
class="size-4 text-ink-gray-7 stroke-1.5 cursor-pointer"
|
class="size-4 text-ink-gray-7 stroke-1.5 cursor-pointer"
|
||||||
@click="toggleMenu(rowIndex)"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="menuOpenIndex === rowIndex"
|
v-if="menuOpenIndex === rowIndex"
|
||||||
class="absolute right-0 z-10 mt-1 w-32 bg-white border border-outline-gray-1 rounded-md shadow-sm"
|
class="absolute right-[30px] top-5 mt-1 w-32 bg-surface-white border border-outline-gray-1 rounded-md shadow-sm"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
@click="deleteRow(rowIndex)"
|
@click="deleteRow(rowIndex)"
|
||||||
@@ -58,7 +59,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add Row Button -->
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<Button @click="addRow">
|
<Button @click="addRow">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
@@ -77,7 +77,9 @@ import { Ellipsis, Plus, Trash2 } from 'lucide-vue-next'
|
|||||||
import { onClickOutside } from '@vueuse/core'
|
import { onClickOutside } from '@vueuse/core'
|
||||||
|
|
||||||
const rows = defineModel<Cell[][]>()
|
const rows = defineModel<Cell[][]>()
|
||||||
|
const menuRef = ref(null)
|
||||||
const menuOpenIndex = ref<number | null>(null)
|
const menuOpenIndex = ref<number | null>(null)
|
||||||
|
const menuTopPosition = ref<string>('')
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:modelValue', value: Cell[][]): void
|
(e: 'update:modelValue', value: Cell[][]): void
|
||||||
}>()
|
}>()
|
||||||
@@ -87,7 +89,6 @@ type Cell = {
|
|||||||
editable?: boolean
|
editable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
modelValue?: Cell[][]
|
modelValue?: Cell[][]
|
||||||
@@ -121,12 +122,11 @@ const getGridTemplateColumns = () => {
|
|||||||
return [...Array(columns.value.length).fill('1fr'), '0.25fr'].join(' ')
|
return [...Array(columns.value.length).fill('1fr'), '0.25fr'].join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleMenu = (index: number) => {
|
const toggleMenu = (index: number, event: MouseEvent) => {
|
||||||
menuOpenIndex.value = menuOpenIndex.value === index ? null : index
|
menuOpenIndex.value = menuOpenIndex.value === index ? null : index
|
||||||
|
menuTopPosition.value = `${event.clientY + 10}px`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: Close menu when clicking outside
|
|
||||||
const menuRef = ref(null)
|
|
||||||
onClickOutside(menuRef, () => {
|
onClickOutside(menuRef, () => {
|
||||||
menuOpenIndex.value = null
|
menuOpenIndex.value = null
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ const assessmentTypes = computed(() => {
|
|||||||
return [
|
return [
|
||||||
{ label: 'Quiz', value: 'LMS Quiz' },
|
{ label: 'Quiz', value: 'LMS Quiz' },
|
||||||
{ label: 'Assignment', value: 'LMS Assignment' },
|
{ label: 'Assignment', value: 'LMS Assignment' },
|
||||||
|
{ label: 'Programming Exercise', value: 'LMS Programming Exercise' },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="show" :options="{ size: '2xl' }">
|
<Dialog v-model="show" :options="{ size: '5xl' }">
|
||||||
<template #body-title>
|
<template #body-title>
|
||||||
<div class="text-xl font-semibold text-ink-gray-9">
|
<div class="text-xl font-semibold text-ink-gray-9">
|
||||||
{{
|
{{
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #body-content>
|
<template #body-content>
|
||||||
|
<div class="grid grid-cols-2 gap-10">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<FormControl
|
<FormControl
|
||||||
v-model="exercise.title"
|
v-model="exercise.title"
|
||||||
@@ -23,6 +24,17 @@
|
|||||||
:options="languageOptions"
|
:options="languageOptions"
|
||||||
:required="true"
|
:required="true"
|
||||||
/>
|
/>
|
||||||
|
<ChildTable
|
||||||
|
v-model="exercise.test_cases"
|
||||||
|
:columns="testCaseColumns"
|
||||||
|
:required="true"
|
||||||
|
:addable="true"
|
||||||
|
:deletable="true"
|
||||||
|
:editable="true"
|
||||||
|
:placeholder="__('Add Test Case')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-xs text-ink-gray-5 mb-2">
|
<div class="text-xs text-ink-gray-5 mb-2">
|
||||||
{{ __('Problem Statement') }}
|
{{ __('Problem Statement') }}
|
||||||
@@ -33,18 +45,10 @@
|
|||||||
@change="(val: string) => (exercise.problem_statement = val)"
|
@change="(val: string) => (exercise.problem_statement = val)"
|
||||||
:editable="true"
|
:editable="true"
|
||||||
:fixedMenu="true"
|
:fixedMenu="true"
|
||||||
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
|
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[21rem] overflow-y-auto"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ChildTable
|
</div>
|
||||||
v-model="exercise.test_cases"
|
|
||||||
:columns="testCaseColumns"
|
|
||||||
:required="true"
|
|
||||||
:addable="true"
|
|
||||||
:deletable="true"
|
|
||||||
:editable="true"
|
|
||||||
:placeholder="__('Add Test Case')"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #actions="{ close }">
|
<template #actions="{ close }">
|
||||||
@@ -65,7 +69,9 @@
|
|||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'ProgrammingExerciseSubmissions',
|
name: 'ProgrammingExerciseSubmissions',
|
||||||
params: { exerciseID: props.exerciseID },
|
query: {
|
||||||
|
exercise: props.exerciseID,
|
||||||
|
},
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<Button>
|
<Button>
|
||||||
@@ -105,12 +111,14 @@ const exercises = defineModel<{
|
|||||||
) => void
|
) => void
|
||||||
}
|
}
|
||||||
}>('exercises')
|
}>('exercises')
|
||||||
|
|
||||||
const exercise = ref<ProgrammingExercise>({
|
const exercise = ref<ProgrammingExercise>({
|
||||||
title: '',
|
title: '',
|
||||||
language: 'Python',
|
language: 'Python',
|
||||||
problem_statement: '',
|
problem_statement: '',
|
||||||
test_cases: [],
|
test_cases: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const languageOptions = [
|
const languageOptions = [
|
||||||
{ label: 'Python', value: 'Python' },
|
{ label: 'Python', value: 'Python' },
|
||||||
{ label: 'JavaScript', value: 'JavaScript' },
|
{ label: 'JavaScript', value: 'JavaScript' },
|
||||||
@@ -159,6 +167,8 @@ const fetchTestCases = () => {
|
|||||||
testCases.update({
|
testCases.update({
|
||||||
filters: {
|
filters: {
|
||||||
parent: props.exerciseID,
|
parent: props.exerciseID,
|
||||||
|
parenttype: 'LMS Programming Exercise',
|
||||||
|
parentfield: 'test_cases',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
testCases.reload()
|
testCases.reload()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-outline-gray-2 prose-th:border-outline-gray-2 prose-td:relative prose-th:relative prose-th:bg-surface-gray-2 prose-sm max-w-none !whitespace-normal border-r px-5 py-2 h-full"
|
class="ProseMirror prose prose-table:table-fixed prose-td:p-2 prose-th:p-2 prose-td:border prose-th:border prose-td:border-outline-gray-2 prose-th:border-outline-gray-2 prose-td:relative prose-th:relative prose-th:bg-surface-gray-2 prose-sm max-w-none !whitespace-normal border-r px-5 py-2 h-full"
|
||||||
></div>
|
></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center justify-between p-2 bg-surface-gray-1">
|
<div class="flex items-center justify-between p-2 bg-surface-gray-2">
|
||||||
<div class="font-semibold">
|
<div class="font-semibold">
|
||||||
{{ exercise.doc?.language }}
|
{{ exercise.doc?.language }}
|
||||||
</div>
|
</div>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
maxHeight="1000px"
|
maxHeight="1000px"
|
||||||
/>
|
/>
|
||||||
<span v-if="error" class="text-xs text-ink-gray-5 px-2">
|
<span v-if="error" class="text-xs text-ink-gray-5 px-2">
|
||||||
{{ __('Compiler Error') }}:
|
{{ __('Compiler Message') }}:
|
||||||
</span>
|
</span>
|
||||||
<textarea
|
<textarea
|
||||||
v-if="error"
|
v-if="error"
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
/>
|
/>
|
||||||
<!-- <textarea v-else v-model="output" class="bg-surface-gray-1 border-none text-sm h-28 leading-6" readonly /> -->
|
<!-- <textarea v-else v-model="output" class="bg-surface-gray-1 border-none text-sm h-28 leading-6" readonly /> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref="testCaseSection" v-if="testCases.length" class="p-3">
|
<div ref="testCaseSection" v-if="testCases.length" class="p-3">
|
||||||
<span class="text-lg font-semibold text-ink-gray-9">
|
<span class="text-lg font-semibold text-ink-gray-9">
|
||||||
{{ __('Test Cases') }}
|
{{ __('Test Cases') }}
|
||||||
@@ -132,7 +133,7 @@ const testCases = ref<
|
|||||||
}>
|
}>
|
||||||
>([])
|
>([])
|
||||||
const boilerplate = ref<string>(
|
const boilerplate = ref<string>(
|
||||||
`with open("stdin", "r") as f:\n data = f.read()\n\ninputs = data.split()\n\n# inputs is a list of strings\n# write your code below\n\n`
|
`with open("stdin", "r") as f:\n data = f.read()\n\ninputs = data.split() if len(data) else []\n\n# inputs is a list of strings\n# write your code below\n\n`
|
||||||
)
|
)
|
||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -179,8 +180,10 @@ watch(
|
|||||||
(doc) => {
|
(doc) => {
|
||||||
if (doc) {
|
if (doc) {
|
||||||
code.value = `${boilerplate.value}${doc.code || ''}\n`
|
code.value = `${boilerplate.value}${doc.code || ''}\n`
|
||||||
|
if (testCases.value.length === 0) {
|
||||||
testCases.value = doc.test_cases || []
|
testCases.value = doc.test_cases || []
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
@@ -207,6 +210,7 @@ const runCode = async () => {
|
|||||||
if (testCaseSection.value) {
|
if (testCaseSection.value) {
|
||||||
testCaseSection.value.scrollIntoView({ behavior: 'smooth' })
|
testCaseSection.value.scrollIntoView({ behavior: 'smooth' })
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const test_case of exercise.doc.test_cases) {
|
for (const test_case of exercise.doc.test_cases) {
|
||||||
let result = await execute(test_case.input)
|
let result = await execute(test_case.input)
|
||||||
if (error.value) {
|
if (error.value) {
|
||||||
@@ -217,7 +221,6 @@ const runCode = async () => {
|
|||||||
}
|
}
|
||||||
let status =
|
let status =
|
||||||
result.trim() === test_case.expected_output.trim() ? 'Passed' : 'Failed'
|
result.trim() === test_case.expected_output.trim() ? 'Passed' : 'Failed'
|
||||||
|
|
||||||
testCases.value.push({
|
testCases.value.push({
|
||||||
input: test_case.input,
|
input: test_case.input,
|
||||||
output: result,
|
output: result,
|
||||||
@@ -256,41 +259,57 @@ const createSubmission = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const execute = async (stdin = '') => {
|
const execute = (stdin = ''): Promise<string> => {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let outputChunks: string[] = []
|
let outputChunks: string[] = []
|
||||||
|
let hasExited = false
|
||||||
|
let hasError = false
|
||||||
|
|
||||||
const session = new LiveCodeSession({
|
let session = new LiveCodeSession({
|
||||||
base_url: 'https://falcon.frappe.io',
|
base_url: 'https://falcon.frappe.io',
|
||||||
runtime: 'python',
|
runtime: 'python',
|
||||||
code: code.value,
|
code: code.value,
|
||||||
files: [{ filename: 'stdin', contents: stdin }],
|
files: [{ filename: 'stdin', contents: stdin }],
|
||||||
onMessage: (msg: any) => {
|
onMessage: (msg: any) => {
|
||||||
|
console.log('msg', msg)
|
||||||
|
|
||||||
if (msg.msgtype === 'write' && msg.file === 'stdout') {
|
if (msg.msgtype === 'write' && msg.file === 'stdout') {
|
||||||
outputChunks.push(msg.data)
|
outputChunks.push(msg.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.msgtype == 'exitstatus') {
|
if (msg.msgtype === 'write' && msg.file === 'stderr') {
|
||||||
if (msg.exitstatus != 0) {
|
hasError = true
|
||||||
|
errorMessage.value = msg.data
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.msgtype === 'exitstatus') {
|
||||||
|
hasExited = true
|
||||||
|
if (msg.exitstatus !== 0) {
|
||||||
error.value = true
|
error.value = true
|
||||||
} else {
|
} else {
|
||||||
error.value = false
|
error.value = false
|
||||||
}
|
}
|
||||||
}
|
resolve(outputChunks.join('').trim())
|
||||||
|
|
||||||
if (msg.msgtype === 'exitstatus') {
|
|
||||||
resolve(outputChunks.join(''))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => reject('Execution timed out.'), 10000)
|
setTimeout(() => {
|
||||||
|
if (!hasExited) {
|
||||||
|
error.value = true
|
||||||
|
errorMessage.value = 'Execution timed out.'
|
||||||
|
reject('Execution timed out.')
|
||||||
|
}
|
||||||
|
}, 20000)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const breadcrumbs = computed(() => {
|
const breadcrumbs = computed(() => {
|
||||||
return [
|
return [
|
||||||
{ label: __('Programming Exercises') },
|
{
|
||||||
|
label: __('Programming Exercise Submissions'),
|
||||||
|
route: { name: 'ProgrammingExerciseSubmissions' },
|
||||||
|
},
|
||||||
{ label: exercise.doc?.title },
|
{ label: exercise.doc?.title },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -129,18 +129,20 @@ import {
|
|||||||
ListRowItem,
|
ListRowItem,
|
||||||
usePageMeta,
|
usePageMeta,
|
||||||
} from 'frappe-ui'
|
} from 'frappe-ui'
|
||||||
import { computed, inject, ref, watch } from 'vue'
|
|
||||||
import { sessionStore } from '@/stores/session'
|
|
||||||
import type {
|
import type {
|
||||||
ProgrammingExerciseSubmission,
|
ProgrammingExerciseSubmission,
|
||||||
Filters,
|
Filters,
|
||||||
} from '@/pages/ProgrammingExercises/types'
|
} from '@/pages/ProgrammingExercises/types'
|
||||||
|
import { computed, inject, onMounted, ref, watch } from 'vue'
|
||||||
|
import { sessionStore } from '@/stores/session'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
import Link from '@/components/Controls/Link.vue'
|
||||||
import EmptyState from '@/components/EmptyState.vue'
|
import EmptyState from '@/components/EmptyState.vue'
|
||||||
|
|
||||||
const { brand } = sessionStore()
|
const { brand } = sessionStore()
|
||||||
const dayjs = inject('$dayjs') as any
|
const dayjs = inject('$dayjs') as any
|
||||||
|
const user = inject('$user') as any
|
||||||
|
const filterFields = ['exercise', 'member', 'status']
|
||||||
const filters = ref<Filters>({
|
const filters = ref<Filters>({
|
||||||
exercise: '',
|
exercise: '',
|
||||||
member: '',
|
member: '',
|
||||||
@@ -148,6 +150,19 @@ const filters = ref<Filters>({
|
|||||||
})
|
})
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!user.data?.is_instructor && !user.data?.is_moderator) {
|
||||||
|
router.push({ name: 'Courses' })
|
||||||
|
}
|
||||||
|
filterFields.forEach((field) => {
|
||||||
|
if (router.currentRoute.value.query[field]) {
|
||||||
|
filters.value[field as keyof Filters] = router.currentRoute.value.query[
|
||||||
|
field
|
||||||
|
] as string
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const submissions = createListResource({
|
const submissions = createListResource({
|
||||||
doctype: 'LMS Programming Exercise Submission',
|
doctype: 'LMS Programming Exercise Submission',
|
||||||
fields: [
|
fields: [
|
||||||
@@ -172,7 +187,6 @@ const submissions = createListResource({
|
|||||||
|
|
||||||
watch(filters.value, () => {
|
watch(filters.value, () => {
|
||||||
let filtersToApply: Record<string, any> = {}
|
let filtersToApply: Record<string, any> = {}
|
||||||
const filterFields = ['exercise', 'member', 'status']
|
|
||||||
filterFields.forEach((field) => {
|
filterFields.forEach((field) => {
|
||||||
if (filters.value[field as keyof Filters]) {
|
if (filters.value[field as keyof Filters]) {
|
||||||
filtersToApply[field] = filters.value[field as keyof Filters]
|
filtersToApply[field] = filters.value[field as keyof Filters]
|
||||||
@@ -211,7 +225,7 @@ const submissionColumns = computed(() => {
|
|||||||
{
|
{
|
||||||
label: __('Exercise'),
|
label: __('Exercise'),
|
||||||
key: 'exercise_title',
|
key: 'exercise_title',
|
||||||
width: '40%',
|
width: '30%',
|
||||||
icon: 'code',
|
icon: 'code',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -225,6 +239,7 @@ const submissionColumns = computed(() => {
|
|||||||
key: 'modified',
|
key: 'modified',
|
||||||
width: '20%',
|
width: '20%',
|
||||||
icon: 'clock',
|
icon: 'clock',
|
||||||
|
align: 'right',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
showForm = true
|
showForm = true
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
class="flex flex-col border rounded-md p-3 h-full hover:border-outline-gray-3 space-y-2"
|
class="flex flex-col border rounded-md p-3 h-full hover:border-outline-gray-3 space-y-2 cursor-pointer"
|
||||||
>
|
>
|
||||||
<div class="text-lg font-semibold text-ink-gray-9">
|
<div class="text-lg font-semibold text-ink-gray-9">
|
||||||
{{ exercise.title }}
|
{{ exercise.title }}
|
||||||
|
|||||||
@@ -216,20 +216,13 @@ const routes = [
|
|||||||
component: () => import('@/pages/PersonaForm.vue'),
|
component: () => import('@/pages/PersonaForm.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/exercises',
|
path: '/programming-exercises',
|
||||||
name: 'ProgrammingExercises',
|
name: 'ProgrammingExercises',
|
||||||
component: () =>
|
component: () =>
|
||||||
import('@/pages/ProgrammingExercises/ProgrammingExercises.vue'),
|
import('@/pages/ProgrammingExercises/ProgrammingExercises.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/exercises/:exerciseID',
|
path: '/programming-exercises/submissions',
|
||||||
name: 'ProgrammingExerciseForm',
|
|
||||||
component: () =>
|
|
||||||
import('@/pages/ProgrammingExercises/ProgrammingExerciseForm.vue'),
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/exercises/submissions',
|
|
||||||
name: 'ProgrammingExerciseSubmissions',
|
name: 'ProgrammingExerciseSubmissions',
|
||||||
component: () =>
|
component: () =>
|
||||||
import(
|
import(
|
||||||
@@ -238,7 +231,7 @@ const routes = [
|
|||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/exercises/:exerciseID/submission/:submissionID',
|
path: '/programming-exercises/:exerciseID/submission/:submissionID',
|
||||||
name: 'ProgrammingExerciseSubmission',
|
name: 'ProgrammingExerciseSubmission',
|
||||||
component: () =>
|
component: () =>
|
||||||
import(
|
import(
|
||||||
|
|||||||
@@ -1544,24 +1544,18 @@ def get_exercise_status(test_cases):
|
|||||||
|
|
||||||
|
|
||||||
def update_test_cases(test_cases, submission):
|
def update_test_cases(test_cases, submission):
|
||||||
|
frappe.db.delete("LMS Test Case Submission", {"parent": submission})
|
||||||
for row in test_cases:
|
for row in test_cases:
|
||||||
test_case = frappe.db.get_value(
|
test_case = frappe.new_doc("LMS Test Case Submission")
|
||||||
"LMS Test Case Submission",
|
test_case.update(
|
||||||
{
|
{
|
||||||
"parent": submission,
|
"parent": submission,
|
||||||
|
"parenttype": "LMS Programming Exercise Submission",
|
||||||
|
"parentfield": "test_cases",
|
||||||
"input": row.get("input"),
|
"input": row.get("input"),
|
||||||
},
|
|
||||||
"name",
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if test_case:
|
|
||||||
frappe.db.set_value(
|
|
||||||
"LMS Test Case Submission",
|
|
||||||
test_case.name,
|
|
||||||
{
|
|
||||||
"output": row.get("output"),
|
"output": row.get("output"),
|
||||||
"expected_output": row.get("expected_output"),
|
"expected_output": row.get("expected_output"),
|
||||||
"status": row.get("status", "Failed"),
|
"status": row.get("status", "Failed"),
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
test_case.insert()
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "input",
|
"fieldname": "input",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
"label": "Input"
|
"label": "Input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -49,8 +50,8 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-06-18 20:06:57.182153",
|
"modified": "2025-06-24 11:23:13.803159",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "sayali@frappe.io",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Test Case Submission",
|
"name": "LMS Test Case Submission",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
|||||||
@@ -1504,6 +1504,9 @@ def get_assessments(batch, member=None):
|
|||||||
elif assessment.assessment_type == "LMS Quiz":
|
elif assessment.assessment_type == "LMS Quiz":
|
||||||
assessment = get_quiz_details(assessment, member)
|
assessment = get_quiz_details(assessment, member)
|
||||||
|
|
||||||
|
elif assessment.assessment_type == "LMS Programming Exercise":
|
||||||
|
assessment = get_exercise_details(assessment, member)
|
||||||
|
|
||||||
return assessments
|
return assessments
|
||||||
|
|
||||||
|
|
||||||
@@ -1576,6 +1579,31 @@ def get_quiz_details(assessment, member):
|
|||||||
return assessment
|
return assessment
|
||||||
|
|
||||||
|
|
||||||
|
def get_exercise_details(assessment, member):
|
||||||
|
assessment.title = frappe.db.get_value(
|
||||||
|
"LMS Programming Exercise", assessment.assessment_name, "title"
|
||||||
|
)
|
||||||
|
filters = {"member": member, "exercise": assessment.assessment_name}
|
||||||
|
|
||||||
|
if frappe.db.exists("LMS Programming Exercise Submission", filters):
|
||||||
|
assessment.submission = frappe.db.get_value(
|
||||||
|
"LMS Programming Exercise Submission",
|
||||||
|
filters,
|
||||||
|
["name", "status"],
|
||||||
|
as_dict=True,
|
||||||
|
)
|
||||||
|
assessment.completed = True
|
||||||
|
assessment.status = assessment.submission.status
|
||||||
|
assessment.edit_url = (
|
||||||
|
f"/exercises/{assessment.assessment_name}/submission/{assessment.submission.name}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
assessment.status = "Not Attempted"
|
||||||
|
assessment.color = "red"
|
||||||
|
assessment.completed = False
|
||||||
|
assessment.edit_url = f"/exercises/{assessment.assessment_name}/submission/new"
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_batch_students(batch):
|
def get_batch_students(batch):
|
||||||
students = []
|
students = []
|
||||||
|
|||||||
Reference in New Issue
Block a user