This commit is contained in:
Neha Ranade 2022-03-14 12:30:59 +00:00
Родитель b870a49dec 05d082df2b
Коммит b193269a5c
23 изменённых файлов: 501 добавлений и 179 удалений

Просмотреть файл

@ -16,8 +16,10 @@ namespace Assessment.App.Database.Model
public List<string> Options { get; set; } public List<string> Options { get; set; }
public int Answer { get; set; } public List<string> Answer { get; set; }
public string TextType{get;set;} public string TextType{get;set;}
public string QuestionType{get;set;}
} }
} }

Просмотреть файл

@ -2,6 +2,6 @@
{ {
public class QuestionResponseInfo public class QuestionResponseInfo
{ {
public int ChosenOption { get; set; } public string[] ChosenOption { get; set; }
} }
} }

Просмотреть файл

@ -9,8 +9,11 @@ namespace Assessment.App.Functions.Student.Dto
public string Name { get; set; } public string Name { get; set; }
public string Description { get; set; } public string Description { get; set; }
public List<string> Options { get; set; } public List<string> Options { get; set; }
public int ChosenOption { get; set; } public string[] ChosenOption { get; set; }
public string TextType{get;set;} public string TextType{get;set;}
public string QuestionType{get;set;}
} }
} }

Просмотреть файл

@ -118,7 +118,7 @@ namespace Assessment.App.Functions.Student
foreach (var questionId in assessmentItem.QuestionIds) foreach (var questionId in assessmentItem.QuestionIds)
{ {
var item = questionItems[questionId]; var item = questionItems[questionId];
var chosenOption = -1; string[] chosenOption = new string[]{};
if (studentResponse.Responses.TryGetValue(questionId, out var responseInfo)) if (studentResponse.Responses.TryGetValue(questionId, out var responseInfo))
{ {
chosenOption = responseInfo.ChosenOption; chosenOption = responseInfo.ChosenOption;
@ -131,7 +131,8 @@ namespace Assessment.App.Functions.Student
Description = item.Description, Description = item.Description,
Options = item.Options, Options = item.Options,
ChosenOption = chosenOption, ChosenOption = chosenOption,
TextType=item.TextType TextType=item.TextType,
QuestionType = item.QuestionType
}); });
} }
@ -177,10 +178,13 @@ namespace Assessment.App.Functions.Student
{ {
if (requestData.Responses.TryGetValue(questionId, out var responseInfo)) if (requestData.Responses.TryGetValue(questionId, out var responseInfo))
{ {
if (responseInfo.ChosenOption == questionItems[questionId].Answer) var questionType = questionItems[questionId].QuestionType;
{ var studentAns = responseInfo.ChosenOption;
correctAnswers += 1; studentAns = studentAns.Select(s => s.ToLowerInvariant()).ToArray();
} var correctAns = questionItems[questionId].Answer;
correctAns = correctAns.ConvertAll(d => d.ToLower());
var res = getResult(studentAns, correctAns.ToArray(), questionType);
correctAnswers = correctAnswers + res;
} }
} }
@ -225,5 +229,27 @@ namespace Assessment.App.Functions.Student
return result; return result;
} }
private static int getResult(string[] studentResponse, string[] correctAns, string questionType){
if (questionType == "MCQ" || questionType == "TF"){
Array.Sort(studentResponse);
Array.Sort(correctAns);
if (studentResponse.SequenceEqual(correctAns)){
return 1;
}
else{
return 0;
}
}
if (questionType == "QA"){
foreach(string answer in studentResponse ){
if (correctAns.Contains(answer)){
return 1;
}
}
return 0;
}
return 0;
}
} }
} }

Просмотреть файл

@ -180,7 +180,8 @@ namespace Assessment.App.Functions.Teacher
Name = questionItem.Name, Name = questionItem.Name,
Options = questionItem.Options, Options = questionItem.Options,
LastModified = DateTime.UtcNow, LastModified = DateTime.UtcNow,
TextType = questionItem.TextType TextType = questionItem.TextType,
QuestionType = questionItem.QuestionType,
}); });
return new OkObjectResult(new CreateQuestionResponse() {Id = id}); return new OkObjectResult(new CreateQuestionResponse() {Id = id});
@ -213,6 +214,8 @@ namespace Assessment.App.Functions.Teacher
Name = questionItem.Name, Name = questionItem.Name,
Options = questionItem.Options, Options = questionItem.Options,
LastModified = DateTime.UtcNow, LastModified = DateTime.UtcNow,
TextType = questionItem.TextType,
QuestionType = questionItem.QuestionType
}); });
return new OkResult(); return new OkResult();
} }

Просмотреть файл

@ -140,7 +140,8 @@ export const AssessmentStatisticsComponent = (
incorrect: 0, incorrect: 0,
} }
} }
if (value.chosenOption === question.answer) { var correctAnswers = new Set(question.answer)
if (correctAnswers.has(value.chosenOption.toString())) {
data[questionId].correct += 1; data[questionId].correct += 1;
} else { } else {
data[questionId].incorrect += 1; data[questionId].incorrect += 1;

Просмотреть файл

@ -6,31 +6,40 @@ import {
Label, Label,
mergeStyles, mergeStyles,
PrimaryButton, PrimaryButton,
TextField TextField,
Dropdown,
ICheckboxProps,
Checkbox,
} from "@fluentui/react"; } from "@fluentui/react";
import {Question} from "../model/Question";
import {Col, Container, Row} from "react-grid-system"; import {Col, Container, Row} from "react-grid-system";
const optionRootClass = mergeStyles({display: 'flex', alignItems: 'baseline'}); const optionRootClass = mergeStyles({display: 'flex', alignItems: 'baseline'});
const textFieldStyles: Partial<ITextFieldStyles> = {fieldGroup: {width: 350}}; const textFieldStyles: Partial<ITextFieldStyles> = {fieldGroup: {width: 350}};
interface EditQuestionComponentProps { interface EditQuestionComponentProps {
question: Question; question: any;
setQuestion: (f: (oldValue: Question) => Question) => void; setQuestion: (f: (oldValue: any ) => any) => void;
} }
export const EditQuestionComponent = ( export const EditQuestionComponent = (
{question, setQuestion}: EditQuestionComponentProps {question, setQuestion}: EditQuestionComponentProps
) => { ) => {
const createOptions = (): IChoiceGroupOption[] => {
const createOneOption = (optionId: number): IChoiceGroupOption => { const shouldCheckBoxBeChecked = (index:number):boolean => {
if (question.answer.find((x: string) => x === index.toString())){
return true;
}
else{
return false;
}
}
const createOptions = (): ICheckboxProps[] => {
const createOneOption = (optionId: number): ICheckboxProps => {
return { return {
key: optionId.toString(), id: optionId.toString(),
text: '', onRenderLabel: (props, render) => {
onRenderField: (props, render) => {
return ( return (
<div className={optionRootClass}> <div className={optionRootClass}>
{render!(props)} {render!(props)}
@ -58,24 +67,129 @@ export const EditQuestionComponent = (
/> />
</div> </div>
) )
} },
onChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => {
var previousArray:string[] = Object.assign([], question.answer)
if (ev?.currentTarget.id !== undefined){
if (checked){
if (previousArray.includes(ev?.currentTarget.id) === false){
previousArray.push(ev?.currentTarget.id)
}
}
else{
const index = previousArray.indexOf(ev?.currentTarget.id, 0);
if (index > -1) {
previousArray.splice(index, 1);
}
}
}
setQuestion({...question, answer:previousArray})
},
defaultChecked: shouldCheckBoxBeChecked(optionId)
}; };
}; };
return question.options.map((_: any, index: number) => createOneOption(index)); return question.options.map((_: any, index: number) => createOneOption(index));
}; };
const updateCorrectAnswer = (_: any, option?: IChoiceGroupOption) => { const updateCorrectAnswer = (_: any, option?: IChoiceGroupOption) => {
var answer: number; var answer: string[];
if (option == null) { if (option == null) {
answer = -1; answer = [];
} else { } else {
answer = Number(option.key); answer = [option.key.toString()];
} }
setQuestion(q => ({...q, answer: answer})) setQuestion(q => ({...q, answer: answer}))
} }
function mcqDisplay(isOn:boolean){
if (isOn){
return <> <Row>
<Col md={2}>
<Label style={{ textAlign: "left" }}>Options</Label>
</Col>
<Col md={6}>
{createOptions().map((value:ICheckboxProps , index: number) => <Checkbox {...value}/>)}
</Col>
</Row><br /><Row>
<Col md={2} />
<Col md={6}>
<PrimaryButton text="Add option" onClick={() => setQuestion(
q => ({ ...q, options: [...q.options, ''] })
)} />
</Col>
</Row></>
}
}
function tfDisplay(isOn:Boolean){
if (isOn){
return <> <Row>
<Col md={2}>
<Label style={{ textAlign: "left" }}>Options</Label>
</Col>
<Col md={6}>
<ChoiceGroup
options={[{key:'0', text:'True'}, {key:'1', text:'False'}]}
required={true}
selectedKey={`${question.answer}`}
onChange={updateCorrectAnswer}/>
</Col>
</Row></>
}
return <></>
}
function qaDisplay(isOn:boolean){
if (isOn){
return <>
<Row>
<Col md={2}>
<Label style={{textAlign: "left"}}>Correct answer/s</Label>
</Col>
<Col md={6}>
{question.answer.map((_: any, index: number) => ( <>
<div className={optionRootClass}>
<TextField
id={`question-answer-${index}`}
styles={textFieldStyles}
value={question.answer[index]}
onChange={(_: any, newValue?: string) => setQuestion(q => {
var result = { ...q, answer: [...q.answer] };
result.answer[index] = newValue || '';
return result;
})} /><IconButton
iconProps={{ iconName: "Delete" }}
onClick={() => {
setQuestion(q => {
let result = { ...q, answer: [...q.answer] };
result.answer.splice(index, 1);
return result;
});
} }
disabled={question.answer.length <= 1} />
</div>
<br></br>
</>))}
</Col>
</Row>
<Row>
<Col md={2} />
<Col md={6}>
<PrimaryButton text="Add answer" onClick={() => setQuestion(
q => ({ ...q, answer: [...q.answer, ''] })
)} />
</Col>
</Row>
</>
}
return <></>
}
return (<Container style={{margin: '30px', position: 'relative'}}> return (<Container style={{margin: '30px', position: 'relative'}}>
<Row> <Row>
<Col md={2}> <Col md={2}>
<Label style={{textAlign: "left"}}>Name</Label> <Label style={{textAlign: "left"}}>Question Name</Label>
</Col> </Col>
<Col md={6}> <Col md={6}>
<TextField <TextField
@ -90,7 +204,7 @@ export const EditQuestionComponent = (
<br/> <br/>
<Row> <Row>
<Col md={2}> <Col md={2}>
<Label style={{textAlign: "left"}}>Description</Label> <Label style={{textAlign: "left"}}>Question Description</Label>
</Col> </Col>
<Col md={6}> <Col md={6}>
<TextField <TextField
@ -108,42 +222,31 @@ export const EditQuestionComponent = (
<br/> <br/>
<Row> <Row>
<Col md={2}> <Col md={2}>
<Label style={{textAlign: "left"}}>Text format</Label> <Label style={{textAlign: "left"}}>Question format</Label>
</Col> </Col>
<Col md={6}> <Col md = {6}>
<TextField <Dropdown
id="textType-input" defaultSelectedKey={question.textType}
rows={1} options={[{text:"Text", key: 'text'}, {text:"HTML", key:'html'}]}
value={question.textType} onChange={(_, key)=> setQuestion({...question, textType:key?.key})} />
onChange={(_: any, newValue?: string) =>
setQuestion(q => ({...q, textType: newValue || ''}))
}
/>
</Col> </Col>
</Row> </Row>
<br/> <br/>
<Row> <Row>
<Col md={2}> <Col md={2}>
<Label style={{textAlign: "left"}}>Options</Label> <Label style={{textAlign: "left"}}>Question Type</Label>
</Col> </Col>
<Col md={6}> <Col md={6}>
<ChoiceGroup <Dropdown
options={createOptions()} defaultSelectedKey={question.questionType}
required={true} options={[{text:"Multiple Choice", key: 'MCQ'}, {text:"True/False", key:'TF'}, {text:"Question/Answer", key:'QA'}]}
selectedKey={`${question.answer}`} onChange={(_, key)=> setQuestion({...question, questionType: key?.key.toString() || '', answer:[], options:[]})} />
onChange={updateCorrectAnswer}/>
</Col> </Col>
</Row> </Row>
<br/> <br/>
<Row> {mcqDisplay(question.questionType === "MCQ")}
<Col md={2}/> {qaDisplay(question.questionType === "QA")}
<Col md={6}> {tfDisplay(question.questionType === "TF")}
<PrimaryButton text="Add option" onClick={() => setQuestion(
q => ({...q, options: [...q.options, '']})
)}/>
</Col>
</Row>
</Container> </Container>
); );
} }

Просмотреть файл

@ -1,63 +1,123 @@
import React from "react"; import React from "react";
import {StudentQuestion} from '../model/StudentQuestion'; import {StudentQuestion} from '../model/StudentQuestion';
import {ChoiceGroup, IChoiceGroupOption} from '@fluentui/react/lib/ChoiceGroup'; import { IChoiceGroupOption} from '@fluentui/react/lib/ChoiceGroup';
import { Label } from "@fluentui/react"; import { Checkbox, ICheckboxProps, Label, TextField } from "@fluentui/react";
import { getTheme } from '@fluentui/react'; import { getTheme } from '@fluentui/react';
import parse from 'html-react-parser'; import parse from 'html-react-parser';
interface StudentQuestionComponentProps { interface StudentQuestionComponentProps {
question: StudentQuestion; question: StudentQuestion;
selectedOption: number; selectedOption: string[];
setSelectedOption: (choice: number) => void; setSelectedOption: (choice: string[]) => void;
} }
var getDisplay = (x:string, texttype:string)=> { var parseDisplay = (x:string, texttype:string)=> {
console.log("text type is"); if (texttype === "html" || texttype === "text/html" || texttype === "HTML"){
console.log(texttype);
if (texttype === "html" || texttype === "text/html"){
x = x.replaceAll('\\n','<br>') x = x.replaceAll('\\n','<br>')
return parse(x) return parse(x)
} }
// Need to handle case where texttype is markdown // Need to handle case where texttype is markdown
return x return x
} }
export const StudentQuestionComponent = ( export const StudentQuestionComponent = (
{question, selectedOption, setSelectedOption}: StudentQuestionComponentProps {question, selectedOption, setSelectedOption}: StudentQuestionComponentProps
) => { ) => {
const theme = getTheme(); const theme = getTheme();
const options: IChoiceGroupOption[] = question.options.map((value, index) => ({
key: index.toString(), const shouldCheckBoxBeChecked = (index:number):boolean => {
text: value, if (index in selectedOption){
})); return true;
return ( }
<> else{
<br/> return false;
<div style={{ }
backgroundColor: '#faf9f8', }
width: '50%',
margin: 'auto', const createOptions = (): ICheckboxProps[] => {
padding: '10px', const createOneOption = (optionId: number): ICheckboxProps => {
boxShadow: theme.effects.elevation8 return {
}}> id: optionId.toString().concat(question.id) ,
<br/> defaultChecked: shouldCheckBoxBeChecked(optionId),
<div style={{margin: '30px', textAlign: 'left'}}> onChange: (ev?: React.FormEvent<HTMLElement | HTMLInputElement>, checked?: boolean) => {
<Label style={{textAlign: 'left', fontSize: '25px'}}>Question</Label> var previousArray:string[] = Object.assign([], selectedOption)
<p>{getDisplay(question.description, question.textType)}</p> if (ev?.currentTarget.id !== undefined){
{console.log(question)} if (checked){
<ChoiceGroup if (previousArray.includes(optionId.toString()) === false){
selectedKey={selectedOption.toString()} previousArray.push(optionId.toString())
onChange={(_: any, option) => { }
if (option) { }
setSelectedOption(Number(option.key)); else{
const index = previousArray.indexOf(optionId.toString(), 0);
if (index > -1) {
previousArray.splice(index, 1);
} }
}} }
options={options} }
/> setSelectedOption(previousArray)
},
label:question.options[optionId]
};
};
return question.options.map((_: any, index: number) => createOneOption(index));
};
function displayMCQOrTF(){
return <div style={{margin: '30px', textAlign: 'left'}}>
<Label style={{textAlign: 'left', fontSize: '25px'}}>Question</Label>
<p>
{parseDisplay(question.description, question.textType)}
</p>
<p>
{
createOptions().map((value:ICheckboxProps , index: number) =>
<>
<Checkbox {...value}/>
<br/>
</>)}
</p>
</div>
}
function displayQA(){
return <div style={{margin: '30px', textAlign: 'left'}}>
<Label style={{textAlign: 'left', fontSize: '25px'}}>Question</Label>
<p>{parseDisplay(question.description, question.textType)}</p>
<TextField
id="LA-input"
onChange={(_: any, newValue?: string) =>
setSelectedOption([newValue|| ''])}
/>
</div>
}
if (question.questionType == "QA") {
return <>
<br />
<div style={{backgroundColor: '#faf9f8', width: '50%',margin: 'auto', padding: '10px', boxShadow: theme.effects.elevation8}}>
<br />
{displayQA()}
</div>
</>
}
else{
return (
<>
<br/>
<div style={{
backgroundColor: '#faf9f8',
width: '50%',
margin: 'auto',
padding: '10px',
boxShadow: theme.effects.elevation8
}}>
<br/>
{displayMCQOrTF()}
</div> </div>
<br/> </>
<br/> )
</div> }
</>
)
} }

Просмотреть файл

@ -187,8 +187,9 @@ export class FakeRepository implements IRepository {
description: "Applications of machine learning are all around us", description: "Applications of machine learning are all around us",
lastModified: new Date(), lastModified: new Date(),
options: ["True", "False"], options: ["True", "False"],
answer: 0, answer: ['0'],
textType:"text", textType:"text",
questionType:"TF"
}, },
'1': { '1': {
id: '1', id: '1',
@ -200,8 +201,9 @@ export class FakeRepository implements IRepository {
"The use of neural networks", "The use of neural networks",
"Deep learning is used in robots", "Deep learning is used in robots",
], ],
answer: 1, answer: ['1'],
textType:"text" textType:"text",
questionType:"MCQ",
}, },
'2': { '2': {
id: '2', id: '2',
@ -213,8 +215,9 @@ export class FakeRepository implements IRepository {
"To customise a shopping experience based on the type of the customer", "To customise a shopping experience based on the type of the customer",
"Both of the above", "Both of the above",
], ],
answer: 2, answer: ['2'],
textType:"text" textType:"text",
questionType:"MCQ"
}, },
'3': { '3': {
id: '3', id: '3',
@ -227,8 +230,9 @@ export class FakeRepository implements IRepository {
"Documentations", "Documentations",
"All of the above", "All of the above",
], ],
answer: 3, answer: ['3'],
textType:"text" textType:"text",
questionType:"MCQ",
}, },
'4': { '4': {
id: '4', id: '4',
@ -241,8 +245,9 @@ export class FakeRepository implements IRepository {
"System Models", "System Models",
"Software Models", "Software Models",
], ],
answer: 1, answer: ['1'],
textType:"text" textType:"text",
questionType:"MCQ"
}, },
'5': { '5': {
id: '5', id: '5',
@ -255,8 +260,9 @@ export class FakeRepository implements IRepository {
"3", "3",
"5", "5",
], ],
answer: 2, answer: ['2'],
textType:"text" textType:"text",
questionType:"MCQ"
}, },
'6': { '6': {
id: '6', id: '6',
@ -269,8 +275,9 @@ export class FakeRepository implements IRepository {
"Increasing complexity", "Increasing complexity",
"Self-regulation", "Self-regulation",
], ],
answer: 0, answer:['0'],
textType:"text" textType:"text",
questionType:"MCQ"
}, },
'7': { '7': {
id: '7', id: '7',
@ -283,8 +290,9 @@ export class FakeRepository implements IRepository {
"Maintenance", "Maintenance",
"All of the above", "All of the above",
], ],
answer: 3, answer: ['3'],
textType:"text" textType:"text",
questionType:"MCQ"
}, },
'8': { '8': {
id: '8', id: '8',
@ -297,8 +305,9 @@ export class FakeRepository implements IRepository {
"Software Quality Management", "Software Quality Management",
"All of the above", "All of the above",
], ],
answer: 3, answer: ['3'],
textType:"text" textType:"text",
questionType:"MCQ"
}, },
} }
} }
@ -363,7 +372,7 @@ export class FakeRepository implements IRepository {
throw new Error("Not implemented"); throw new Error("Not implemented");
} }
public async submitStudentAssessment(assessmentId: string, chosenOptions: { [id: string]: number }) {} public async submitStudentAssessment(assessmentId: string, chosenOptions: { [id: string]: string[] }) {}
public isReady(): boolean { public isReady(): boolean {
return true; return true;

Просмотреть файл

@ -22,7 +22,7 @@ export interface IRepository {
getAssessmentStats(assessmentId: string): Promise<AssessmentStatistics>; getAssessmentStats(assessmentId: string): Promise<AssessmentStatistics>;
getStudentAssessment(assessmentId: string): Promise<StudentAssessment>; getStudentAssessment(assessmentId: string): Promise<StudentAssessment>;
getStudentQuestions(assessmentId: string): Promise<StudentAssessmentQuestions>; getStudentQuestions(assessmentId: string): Promise<StudentAssessmentQuestions>;
submitStudentAssessment(assessmentId: string, chosenOptions: { [id: string]: number }): void; submitStudentAssessment(assessmentId: string, chosenOptions: { [id: string]: string[] }): void;
deleteQuestions(questionIds: string[]): void; deleteQuestions(questionIds: string[]): void;
deleteQuestionBanks(questionBankIds: string[]): void; deleteQuestionBanks(questionBankIds: string[]): void;
isReady(): boolean; isReady(): boolean;

Просмотреть файл

@ -1,9 +1,23 @@
export interface Question { export abstract class Question {
id: string, id: string;
name: string, name: string;
description: string, description: string;
lastModified: Date, lastModified: Date;
options: string[], answer: string[];
answer: number, textType:string;
textType:string, questionType:string;
options:string[]
constructor(id:string, name:string, description:string, lastModified:Date
,answer:any, textType:string, questionType:string){
this.id= id;
this.name=name;
this.description=description;
this.lastModified=lastModified;
this.textType="text";
this.answer=[''];
this.questionType=questionType
this.options=['', '']
}
} }

Просмотреть файл

@ -1,3 +1,3 @@
export interface QuestionResponseInfo { export interface QuestionResponseInfo {
chosenOption: number, chosenOption: string[],
} }

Просмотреть файл

@ -220,7 +220,7 @@ export class Repository implements IRepository {
} }
public async submitStudentAssessment(assessmentId: string, chosenOptions: { [id: string]: number }) { public async submitStudentAssessment(assessmentId: string, chosenOptions: { [id: string]: string[] }) {
const accessToken = await this.getAccessToken(); const accessToken = await this.getAccessToken();
const request: SubmitStudentAssessmentRequest = { const request: SubmitStudentAssessmentRequest = {
responses: Object.entries(chosenOptions).reduce((a, item) => ({ responses: Object.entries(chosenOptions).reduce((a, item) => ({

Просмотреть файл

@ -3,6 +3,7 @@ export interface StudentQuestion {
name: string, name: string,
description: string, description: string,
options: string[], options: string[],
chosenOption: number, chosenOption: string[],
textType:string, textType:string,
questionType:string
} }

Просмотреть файл

@ -7,8 +7,6 @@ import { Question } from "../Question";
export class GiftParser extends AssessmentAppParser{ export class GiftParser extends AssessmentAppParser{
public parse(): void { public parse(): void {
var questionBankTitle:string = "Not defined yet"; var questionBankTitle:string = "Not defined yet";
var questions:Question[] = []; var questions:Question[] = [];
@ -24,12 +22,13 @@ export class GiftParser extends AssessmentAppParser{
var choices:TextChoice[] = q.choices; var choices:TextChoice[] = q.choices;
var answerTexts = Array(); var answerTexts = Array();
var correctAnswer = 0; var correctAnswer = [];
for (var choice in choices){ for (var choice in choices){
var details:TextChoice = choices[choice]; var details:TextChoice = choices[choice];
answerTexts.push(this.removeTags(details.text['text'])); answerTexts.push(this.removeTags(details.text['text']));
if (details.isCorrect){ var weight = details.weight
correctAnswer = +choice; // plus operator converts to number if (weight != null && weight > 0){
correctAnswer.push(choice); // plus operator converts to number
} }
} }
var stem:TextFormat = q.stem; var stem:TextFormat = q.stem;
@ -40,7 +39,8 @@ export class GiftParser extends AssessmentAppParser{
lastModified: new Date (), lastModified: new Date (),
options: answerTexts, options: answerTexts,
answer: correctAnswer, answer: correctAnswer,
textType:stem.format textType:stem.format,
questionType: "MCQ",
} }
questions.push(question); questions.push(question);
@ -55,13 +55,27 @@ export class GiftParser extends AssessmentAppParser{
description: stem.text, description: stem.text,
lastModified: new Date (), lastModified: new Date (),
options: ["True", "False"], options: ["True", "False"],
answer: ans? 0:1, answer: ans? ['0']:['1'],
textType:stem.format textType:stem.format,
questionType: "TF",
} }
questions.push(question); questions.push(question);
} }
if (q.type === "Description"|| q.type === "Essay" || q.type === "Short"){
var stem:TextFormat = q.stem;
const question: Question = {
id: "",
name: this.removeTags(stem.text),
description: stem.text,
lastModified: new Date (),
options: ["", ""],
answer: ["This is an example"], // Gift format does not give "ideal" anwer for these types
textType:stem.format,
questionType:"QA",
}
questions.push(question);
}
} }
var qb: ParsedQuestionBank = { var qb: ParsedQuestionBank = {

Просмотреть файл

@ -15,12 +15,12 @@ export class MicrosoftOSCParser extends AssessmentAppParser{
var questions:Question[] = []; var questions:Question[] = [];
for (let question of rawQuestion.quiz){ for (let question of rawQuestion.quiz){
var answerTexts = Array(); var answerTexts = Array();
var correctAnswer = 0; var correctAnswer = [];
var counter = 0; var counter = 0;
for (let option of question.answerOptions){ for (let option of question.answerOptions){
answerTexts.push(option.answerText) answerTexts.push(option.answerText)
if (option.isCorrect == "true"){ if (option.isCorrect == "true"){
correctAnswer = counter ; correctAnswer.push(counter.toString());
} }
counter = counter + 1; counter = counter + 1;
@ -28,12 +28,13 @@ export class MicrosoftOSCParser extends AssessmentAppParser{
const questionToSave: Question = { const questionToSave: Question = {
id: "", id: "",
name: question.questionText, name: question.questionText,
description: question.questionText, description: question.questionText,
lastModified: new Date (), lastModified: new Date(),
options: answerTexts, options: answerTexts,
answer: correctAnswer, answer: correctAnswer,
textType:"text" textType: "text",
questionType: "MCQ"
} }
questions.push(questionToSave); questions.push(questionToSave);
} }

Просмотреть файл

@ -2,6 +2,7 @@ import { ParsedQuestionBank } from "./ParsedQuestionBank";
import { AssessmentAppParser } from "./Parser"; import { AssessmentAppParser } from "./Parser";
import { Question } from "../Question"; import { Question } from "../Question";
// Currently only supports MCQs and TFs
export class OriginalAppParser extends AssessmentAppParser{ export class OriginalAppParser extends AssessmentAppParser{
public parse() { public parse() {
const rawData = JSON.parse(this.raw); const rawData = JSON.parse(this.raw);
@ -13,10 +14,11 @@ export class OriginalAppParser extends AssessmentAppParser{
id: "", id: "",
name: rawQuestion.name, name: rawQuestion.name,
description: rawQuestion.description, description: rawQuestion.description,
lastModified: new Date (), lastModified: new Date(),
options: rawQuestion.options, options: rawQuestion.options,
answer: rawQuestion.answer, answer: rawQuestion.answer,
textType: rawQuestion.textType textType: rawQuestion.textType,
questionType: rawQuestion.questionType
} }
questions.push(question); questions.push(question);
} }

Просмотреть файл

@ -20,6 +20,7 @@ export class AssessmentAppParser {
str = str.toString(); str = str.toString();
str = str.replace( /(<([^>]+)>)/ig, '') str = str.replace( /(<([^>]+)>)/ig, '')
str = str.replaceAll('\\n','') str = str.replaceAll('\\n','')
str = str.replace(/\&nbsp;/g, "")
return str;} return str;}
} }

Просмотреть файл

@ -3,10 +3,16 @@ import { ParsedQuestionBank } from "./ParsedQuestionBank";
import { AssessmentAppParser } from "./Parser"; import { AssessmentAppParser } from "./Parser";
import { Question } from "../Question"; import { Question } from "../Question";
interface questionTypeSpecificParse {
correctAnswer:string[]
options:string[]
};
export class QTIParser extends AssessmentAppParser{ export class QTIParser extends AssessmentAppParser{
options = { options = {
ignoreAttributes:false ignoreAttributes:false
}; };
public parse() { public parse() {
var questionBankTitle:string = "Not defined yet"; var questionBankTitle:string = "Not defined yet";
var questions:Question[] = []; var questions:Question[] = [];
@ -19,40 +25,57 @@ export class QTIParser extends AssessmentAppParser{
for (let questionId in questionsSection){ for (let questionId in questionsSection){
var currQuestion = questionsSection[questionId]; var currQuestion = questionsSection[questionId];
// Get question title
var questionTitle = currQuestion['@_title']; var questionTitle = currQuestion['@_title'];
// Get question type
var qMetaDataField = currQuestion['itemmetadata']['qtimetadata']['qtimetadatafield']; var qMetaDataField = currQuestion['itemmetadata']['qtimetadata']['qtimetadatafield'];
var metaData = qMetaDataField[0]; // 0 position contains question type var metaData = qMetaDataField[0]; // 0 position contains question type
if (metaData['fieldentry'] != 'multiple_choice_question'){ var questionType = 'NA';
continue; // As we currently only support MCQs
} // Get question description
var questionText = currQuestion['presentation']['material']['mattext']['#text']; var questionText = currQuestion['presentation']['material']['mattext']['#text'];
questionText = questionText.split('\n')[1];
// Get all options // Get text type
var responseLabels = currQuestion['presentation']['response_lid']['render_choice']['response_label']; var cleanedTextType:string = 'text'
var answerTexts = Array(); if (currQuestion['presentation']['material']['mattext']['@_texttype'] === "text/html"){
var correctAnswer = currQuestion['resprocessing']['respcondition']['conditionvar']['varequal']['#text'] cleanedTextType = "html";
for (let responseId in responseLabels){
var response = responseLabels[responseId];
if (correctAnswer == response['@_ident']){
correctAnswer = responseId;
}
answerTexts.push(this.removeTags(response['material']['mattext']['#text']))
} }
// Finally creating the question // Get answer and options
var result:questionTypeSpecificParse = {options:[], correctAnswer:[]}
if (metaData['fieldentry'] === 'multiple_choice_question' ){
result = this.parseMCQ(currQuestion);
questionType = "MCQ";
}
else if (metaData['fieldentry'] === 'true_false_question'){
result = this.parseMCQ(currQuestion);
questionType = "TF";
}
else if (metaData['fieldentry'] === 'multiple_answers_question'){
result= this.parseMAQ(currQuestion);
questionType = "MCQ";
}
else if (metaData['fieldentry'] === 'numerical_question'){
result= this.parseQA(currQuestion);
questionType = "QA"
}
else{
continue;
}
const question:Question = { const question:Question = {
id: "", id: "",
name: questionTitle, name: questionTitle,
description: questionText, description: questionText,
lastModified: new Date (), lastModified: new Date(),
options: answerTexts, options: result.options,
answer: correctAnswer, answer: result.correctAnswer,
textType:currQuestion['presentation']['material']['mattext']['@_texttype'], textType: cleanedTextType,
} questionType: questionType
}
questions.push(question); questions.push(question);
} }
var qb: ParsedQuestionBank = { var qb: ParsedQuestionBank = {
questionBankTitle: questionBankTitle, questionBankTitle: questionBankTitle,
questions:questions questions:questions
@ -61,6 +84,63 @@ export class QTIParser extends AssessmentAppParser{
} }
private parseMCQ(currQuestion:any){
// Get all options
var responseLabels = currQuestion['presentation']['response_lid']['render_choice']['response_label'];
var answerTexts = Array();
var correctAnswerId = currQuestion['resprocessing']['respcondition']['conditionvar']['varequal']['#text'].toString();
var correctAnswer = ['-1'];
for (let responseId in responseLabels){
var response = responseLabels[responseId];
if (correctAnswerId === response['@_ident'].toString()){
correctAnswer = [responseId.toString()];
}
answerTexts.push(this.removeTags(response['material']['mattext']['#text']))
}
var result: questionTypeSpecificParse = {options:answerTexts, correctAnswer:correctAnswer}
return result
}
private parseMAQ(currQuestion:any){
// Get all options
var responseLabels = currQuestion['presentation']['response_lid']['render_choice']['response_label'];
var answerTexts = Array();
var correctAnswerQTIIds = currQuestion['resprocessing']['respcondition']['conditionvar']['and']['varequal']
var correctAnsIds = new Set();
for (let id in correctAnswerQTIIds){
var answerId = correctAnswerQTIIds[id]['#text'];
correctAnsIds.add(answerId.toString());
}
var correctAns:string[] = Array();
for (let responseId in responseLabels){
var response = responseLabels[responseId];
if (correctAnsIds.has(response['@_ident'].toString())){
var correctAnswer = +responseId
correctAns.push(correctAnswer.toString())
}
answerTexts.push(this.removeTags(response['material']['mattext']['#text']))
}
var result: questionTypeSpecificParse = {options:answerTexts, correctAnswer:correctAns}
return result
}
private parseQA(currQuestion:any){
//Get all allowed answers
var correctAnswerQTIIds = currQuestion['resprocessing']['respcondition']
var correctAns = new Array();
for (let index in correctAnswerQTIIds){
var curr = correctAnswerQTIIds[index]['conditionvar']['or']['varequal']['#text'];
correctAns.push(curr.toString());
}
var result: questionTypeSpecificParse = {options:[], correctAnswer:correctAns}
return result
}
} }

Просмотреть файл

@ -1,5 +1,4 @@
import {PrimaryButton, Spinner} from '@fluentui/react'; import {PrimaryButton, Spinner} from '@fluentui/react';
import * as React from 'react';
import {useContext, useEffect, useState} from 'react'; import {useContext, useEffect, useState} from 'react';
import {StudentQuestionComponent} from '../../components/StudentQuestionComponent'; import {StudentQuestionComponent} from '../../components/StudentQuestionComponent';
import {StudentQuestion} from '../../model/StudentQuestion'; import {StudentQuestion} from '../../model/StudentQuestion';
@ -17,7 +16,7 @@ export const StudentQuiz = () => {
const {id} = useParams<StudentQuizParams>(); const {id} = useParams<StudentQuizParams>();
const repositoryContext = useContext(RepositoryContext); const repositoryContext = useContext(RepositoryContext);
const [questions, setQuestions] = useState<StudentQuestion[]>([]) const [questions, setQuestions] = useState<StudentQuestion[]>([])
const [chosenOptions, setChosenOptions] = useState<{ [id: string]: number }>({}); const [chosenOptions, setChosenOptions] = useState<{ [id: string]: any }>({});
const [isLoaded, setIsLoaded] = useState(false); const [isLoaded, setIsLoaded] = useState(false);
const [studentAssessment, setStudentAssessment] = useState<StudentAssessment | null>(null); const [studentAssessment, setStudentAssessment] = useState<StudentAssessment | null>(null);
const history = useHistory(); const history = useHistory();

Просмотреть файл

@ -20,8 +20,9 @@ export const NewQuestionPage = () => {
description: "", description: "",
lastModified: new Date(), lastModified: new Date(),
options: ['', ''], options: ['', ''],
answer: -1, answer: [],
textType:"" textType:"",
questionType:"MCQ",
}); });
const {bankId} = useParams<NewQuestionPageParams>(); const {bankId} = useParams<NewQuestionPageParams>();
const repositoryContext = React.useContext(RepositoryContext); const repositoryContext = React.useContext(RepositoryContext);
@ -46,7 +47,7 @@ export const NewQuestionPage = () => {
padding: '10px', padding: '10px',
boxShadow: theme.effects.elevation8 boxShadow: theme.effects.elevation8
}}> }}>
<EditQuestionComponent question={question} setQuestion={setQuestion}/> <EditQuestionComponent question={question} setQuestion={setQuestion}/>
<Container style={{margin: '30px', position: 'relative'}}> <Container style={{margin: '30px', position: 'relative'}}>
<Row> <Row>
<Col md={2}/> <Col md={2}/>

Просмотреть файл

@ -21,18 +21,20 @@ export const QuestionPage = () => {
name: "", name: "",
description: "", description: "",
lastModified: new Date(), lastModified: new Date(),
options: ['', ''], questionType: "",
answer: -1, answer: ['-1'],
textType:"text" textType:"text",
options:['', '']
}); });
const [savedQuestion, setSavedQuestion] = useState<Question>({ const [savedQuestion, setSavedQuestion] = useState<Question>({
id: "", id: "",
name: "", name: "",
description: "", description: "",
lastModified: new Date(), lastModified: new Date(),
options: ['', ''], questionType:"",
answer: -1, answer: [],
textType:"text" textType:"text",
options:['', '']
}) })
const {id} = useParams<QuestionPageParams>(); const {id} = useParams<QuestionPageParams>();
const repositoryContext = React.useContext(RepositoryContext); const repositoryContext = React.useContext(RepositoryContext);

Просмотреть файл

@ -105,7 +105,7 @@ export const QuestionBankPage = () => {
if (repositoryContext == null) { if (repositoryContext == null) {
return <p>Question Bank cannot be found</p> return <p>Question Bank cannot be found</p>
} }
const redirectToNewQuestion = () => { const redirectToNewQuestion = () => {
history.push(`/spa/new-question/bank=${id}`); history.push(`/spa/new-question/bank=${id}`);
} }
const _items: ICommandBarItemProps[] = [ const _items: ICommandBarItemProps[] = [