fix: add grading method view
This commit is contained in:
committed by
Feanil Patel
parent
292a457834
commit
2e836a55cf
1
.env
1
.env
@@ -45,7 +45,6 @@ HOTJAR_VERSION=6
|
||||
HOTJAR_DEBUG=false
|
||||
INVITE_STUDENTS_EMAIL_TO=''
|
||||
ENABLE_CHECKLIST_QUALITY=''
|
||||
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
|
||||
# "Multi-level" blocks are unsupported in libraries
|
||||
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content,itembank"
|
||||
# Fallback in local style files
|
||||
|
||||
@@ -48,7 +48,6 @@ HOTJAR_VERSION=6
|
||||
HOTJAR_DEBUG=true
|
||||
INVITE_STUDENTS_EMAIL_TO="someone@domain.com"
|
||||
ENABLE_CHECKLIST_QUALITY=true
|
||||
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
|
||||
# "Multi-level" blocks are unsupported in libraries
|
||||
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content,itembank"
|
||||
# Fallback in local style files
|
||||
|
||||
@@ -40,7 +40,6 @@ ENABLE_TAGGING_TAXONOMY_PAGES=true
|
||||
BBB_LEARN_MORE_URL=''
|
||||
INVITE_STUDENTS_EMAIL_TO="someone@domain.com"
|
||||
ENABLE_CHECKLIST_QUALITY=true
|
||||
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
|
||||
# "Multi-level" blocks are unsupported in libraries
|
||||
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content,itembank"
|
||||
PARAGON_THEME_URLS=
|
||||
|
||||
@@ -173,12 +173,18 @@ export const scoringCardHooks = (scoring, updateSettings, defaultValue) => {
|
||||
updateSettings({ scoring: { ...scoring, weight } });
|
||||
};
|
||||
|
||||
const handleGradingMethodChange = (event) => {
|
||||
const { value } = event.target;
|
||||
updateSettings({ scoring: { ...scoring, gradingMethod: value } });
|
||||
};
|
||||
|
||||
return {
|
||||
attemptDisplayValue,
|
||||
handleUnlimitedChange,
|
||||
handleMaxAttemptChange,
|
||||
handleOnChange,
|
||||
handleWeightChange,
|
||||
handleGradingMethodChange,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -147,6 +147,7 @@ describe('Problem settings hooks', () => {
|
||||
unlimited: false,
|
||||
number: 5,
|
||||
},
|
||||
gradingMethod: 'last_score',
|
||||
};
|
||||
const defaultValue = 1;
|
||||
test('test scoringCardHooks initializes display value when attempts.number is null', () => {
|
||||
@@ -269,6 +270,11 @@ describe('Problem settings hooks', () => {
|
||||
output.handleWeightChange({ target: { value } });
|
||||
expect(updateSettings).toHaveBeenCalledWith({ scoring: { ...scoring, weight: parseFloat(value) } });
|
||||
});
|
||||
test('test handleGradingMethodChange', () => {
|
||||
const value = 'first_score';
|
||||
output.handleGradingMethodChange({ target: { value } });
|
||||
expect(updateSettings).toHaveBeenCalledWith({ scoring: { ...scoring, gradingMethod: value } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Show answer card hooks', () => {
|
||||
|
||||
@@ -203,6 +203,7 @@ SettingsWidget.propTypes = {
|
||||
showanswer: PropTypes.string,
|
||||
showResetButton: PropTypes.bool,
|
||||
rerandomize: PropTypes.string,
|
||||
gradingMethod: PropTypes.string,
|
||||
}).isRequired,
|
||||
images: PropTypes.shape({}).isRequired,
|
||||
isLibrary: PropTypes.bool.isRequired,
|
||||
|
||||
@@ -33,6 +33,7 @@ describe('SettingsWidget', () => {
|
||||
maxAttempts: 2,
|
||||
showanswer: 'finished',
|
||||
showResetButton: false,
|
||||
gradingMethod: 'last_score',
|
||||
},
|
||||
images: {},
|
||||
isLibrary: false,
|
||||
|
||||
@@ -82,6 +82,16 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Points',
|
||||
description: 'Scoring weight input label',
|
||||
},
|
||||
scoringGradingMethodInputLabel: {
|
||||
id: 'authoring.problemeditor.settings.scoring.grading.method.inputLabel',
|
||||
defaultMessage: 'Grading Method',
|
||||
description: 'Grading method input label',
|
||||
},
|
||||
gradingMethodSummary: {
|
||||
id: 'authoring.problemeditor.settings.scoring.grading.method',
|
||||
defaultMessage: '{gradingMethod}',
|
||||
description: 'Summary text for scoring grading method',
|
||||
},
|
||||
unlimitedAttemptsSummary: {
|
||||
id: 'authoring.problemeditor.settings.scoring.unlimited',
|
||||
defaultMessage: 'Unlimited attempts',
|
||||
@@ -107,6 +117,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Specify point weight and the number of answer attempts',
|
||||
description: 'Descriptive text for scoring settings',
|
||||
},
|
||||
scoringSettingsLabelWithGradingMethod: {
|
||||
id: 'authoring.problemeditor.settings.scoring.label.withGradingMethod',
|
||||
defaultMessage: 'Specify grading method, point weight and the number of answer attempts',
|
||||
description: 'Descriptive text for scoring settings when grading method is enabled',
|
||||
},
|
||||
attemptsHint: {
|
||||
id: 'authoring.problemeditor.settings.scoring.attempts.hint',
|
||||
defaultMessage: 'If a default value is not set in advanced settings, unlimited attempts are allowed',
|
||||
@@ -117,6 +132,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'If a value is not set, the problem is worth one point',
|
||||
description: 'Summary text for scoring weight',
|
||||
},
|
||||
gradingMethodHint: {
|
||||
id: 'authoring.problemeditor.settings.scoring.grading.method.hint',
|
||||
defaultMessage: 'Define the grading method for this problem. By default, it is the score of the last submission made by the student.',
|
||||
description: 'Summary text for scoring grading method',
|
||||
},
|
||||
showAnswerSettingsTitle: {
|
||||
id: 'authoring.problemeditor.settings.showAnswer.title',
|
||||
defaultMessage: 'Show answer',
|
||||
|
||||
@@ -8,6 +8,7 @@ import { selectors } from '../../../../../../data/redux';
|
||||
import SettingsOption from '../SettingsOption';
|
||||
import messages from '../messages';
|
||||
import { scoringCardHooks } from '../hooks';
|
||||
import { GradingMethod, GradingMethodKeys } from '../../../../../../data/constants/problem';
|
||||
|
||||
const ScoringCard = ({
|
||||
scoring,
|
||||
@@ -23,28 +24,62 @@ const ScoringCard = ({
|
||||
handleUnlimitedChange,
|
||||
handleMaxAttemptChange,
|
||||
handleWeightChange,
|
||||
handleGradingMethodChange,
|
||||
handleOnChange,
|
||||
attemptDisplayValue,
|
||||
} = scoringCardHooks(scoring, updateSettings, defaultValue);
|
||||
|
||||
const getScoringSummary = (weight, attempts, unlimited) => {
|
||||
const getScoringSummary = (weight, attempts, unlimited, gradingMethod) => {
|
||||
let summary = intl.formatMessage(messages.weightSummary, { weight });
|
||||
summary += ` ${String.fromCharCode(183)} `;
|
||||
summary += unlimited
|
||||
? intl.formatMessage(messages.unlimitedAttemptsSummary)
|
||||
: intl.formatMessage(messages.attemptsSummary, { attempts: attempts || defaultValue });
|
||||
|
||||
const methodMessage = gradingMethod ? GradingMethod[gradingMethod] : null;
|
||||
|
||||
if (methodMessage) {
|
||||
summary += ` ${String.fromCharCode(183)} `;
|
||||
summary += intl.formatMessage(messages.gradingMethodSummary, {
|
||||
gradingMethod: intl.formatMessage(methodMessage),
|
||||
});
|
||||
}
|
||||
|
||||
return summary;
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsOption
|
||||
title={intl.formatMessage(messages.scoringSettingsTitle)}
|
||||
summary={getScoringSummary(scoring.weight, scoring.attempts.number, scoring.attempts.unlimited)}
|
||||
summary={getScoringSummary(scoring.weight, scoring.attempts.number, scoring.attempts.unlimited, scoring.gradingMethod)}
|
||||
className="scoringCard"
|
||||
>
|
||||
<div className="mb-4">
|
||||
<FormattedMessage {...messages.scoringSettingsLabel} />
|
||||
<FormattedMessage {...messages.scoringSettingsLabelWithGradingMethod} />
|
||||
</div>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
as="select"
|
||||
value={scoring.gradingMethod}
|
||||
onChange={handleGradingMethodChange}
|
||||
floatingLabel={intl.formatMessage(messages.scoringGradingMethodInputLabel)}
|
||||
>
|
||||
{Object.values(GradingMethodKeys).map((gradingMethod) => {
|
||||
const optionDisplayName = GradingMethod[gradingMethod];
|
||||
return (
|
||||
<option
|
||||
key={gradingMethod}
|
||||
value={gradingMethod}
|
||||
>
|
||||
{intl.formatMessage(optionDisplayName)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Form.Control>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage {...messages.gradingMethodHint} />
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
type="number"
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
} from '@src/testUtils';
|
||||
import ScoringCard from './ScoringCard';
|
||||
import { selectors } from '../../../../../../data/redux';
|
||||
import { GradingMethodKeys } from '../../../../../../data/constants/problem';
|
||||
|
||||
const { app } = selectors;
|
||||
|
||||
@@ -14,6 +15,7 @@ describe('ScoringCard', () => {
|
||||
unlimited: false,
|
||||
number: 5,
|
||||
},
|
||||
gradingMethod: GradingMethodKeys.LAST_SCORE,
|
||||
updateSettings: jest.fn().mockName('args.updateSettings'),
|
||||
};
|
||||
|
||||
@@ -59,6 +61,17 @@ describe('ScoringCard', () => {
|
||||
expect(props.updateSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call updateSettings when changing grading method', () => {
|
||||
render(<ScoringCard {...props} />);
|
||||
fireEvent.click(screen.getByText('Scoring'));
|
||||
const gradingSelect = screen.getByRole('combobox', { name: 'Grading method' });
|
||||
expect(gradingSelect).toBeInTheDocument();
|
||||
expect(gradingSelect.value).toBe(GradingMethodKeys.LAST_SCORE);
|
||||
|
||||
fireEvent.change(gradingSelect, { target: { value: GradingMethodKeys.HIGHEST_SCORE } });
|
||||
expect(props.updateSettings).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call updateSettings when clicking attempts button', () => {
|
||||
const scoringUnlimited = { ...scoring, attempts: { unlimited: true, number: 0 } };
|
||||
render(<ScoringCard {...props} scoring={scoringUnlimited} />);
|
||||
|
||||
@@ -33,6 +33,7 @@ class ReactStateSettingsParser {
|
||||
|
||||
settings = popuplateItem(settings, 'number', 'max_attempts', stateSettings.scoring.attempts, defaultSettings?.maxAttempts, true);
|
||||
settings = popuplateItem(settings, 'weight', 'weight', stateSettings.scoring);
|
||||
settings = popuplateItem(settings, 'gradingMethod', 'grading_method', stateSettings.scoring);
|
||||
settings = popuplateItem(settings, 'on', 'showanswer', stateSettings.showAnswer, defaultSettings?.showanswer, true);
|
||||
if (includes(numberOfAttemptsChoice, stateSettings.showAnswer.on)) {
|
||||
settings = popuplateItem(settings, 'afterAttempts', 'attempts_before_showanswer_button', stateSettings.showAnswer);
|
||||
|
||||
@@ -43,6 +43,7 @@ export const parseScoringSettings = (metadata, defaultSettings) => {
|
||||
scoring = { ...scoring, attempts };
|
||||
|
||||
scoring = popuplateItem(scoring, 'weight', 'weight', metadata);
|
||||
scoring = popuplateItem(scoring, 'grading_method', 'gradingMethod', metadata);
|
||||
|
||||
return scoring;
|
||||
};
|
||||
|
||||
@@ -364,6 +364,32 @@ export const RandomizationTypes = StrictDict({
|
||||
},
|
||||
} as const);
|
||||
|
||||
export const GradingMethodKeys = StrictDict({
|
||||
LAST_SCORE: 'last_score',
|
||||
HIGHEST_SCORE: 'highest_score',
|
||||
AVERAGE_SCORE: 'average_score',
|
||||
FIRST_SCORE: 'first_score',
|
||||
});
|
||||
|
||||
export const GradingMethod = StrictDict({
|
||||
[GradingMethodKeys.LAST_SCORE]: {
|
||||
id: 'authoring.problemeditor.settings.gradingmethod.last_score',
|
||||
defaultMessage: 'Last score (Default)',
|
||||
},
|
||||
[GradingMethodKeys.HIGHEST_SCORE]: {
|
||||
id: 'authoring.problemeditor.settings.gradingmethod.highest_score',
|
||||
defaultMessage: 'Highest score',
|
||||
},
|
||||
[GradingMethodKeys.AVERAGE_SCORE]: {
|
||||
id: 'authoring.problemeditor.settings.gradingmethod.average_score',
|
||||
defaultMessage: 'Average score',
|
||||
},
|
||||
[GradingMethodKeys.FIRST_SCORE]: {
|
||||
id: 'authoring.problemeditor.settings.gradingmethod.first_score',
|
||||
defaultMessage: 'First score',
|
||||
},
|
||||
});
|
||||
|
||||
export const RichTextProblems = [ProblemTypeKeys.SINGLESELECT, ProblemTypeKeys.MULTISELECT] as const;
|
||||
|
||||
export const settingsOlxAttributes = [
|
||||
@@ -374,6 +400,7 @@ export const settingsOlxAttributes = [
|
||||
'@_show_reset_button',
|
||||
'@_submission_wait_seconds',
|
||||
'@_attempts_before_showanswer_button',
|
||||
'@_grading_method',
|
||||
] as const;
|
||||
|
||||
export const ignoredOlxAttributes = [
|
||||
|
||||
@@ -2,7 +2,7 @@ import { has } from 'lodash';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { indexToLetterMap } from '../../../containers/ProblemEditor/data/OLXParser';
|
||||
import { StrictDict } from '../../../utils';
|
||||
import { ProblemTypeKeys, RichTextProblems } from '../../constants/problem';
|
||||
import { GradingMethodKeys, ProblemTypeKeys, RichTextProblems } from '../../constants/problem';
|
||||
import { ToleranceTypes } from '../../../containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Tolerance/constants';
|
||||
import type { EditorState } from '..';
|
||||
|
||||
@@ -29,6 +29,7 @@ const initialState: EditorState['problem'] = {
|
||||
unlimited: true,
|
||||
number: null,
|
||||
},
|
||||
gradingMethod: GradingMethodKeys.LAST_SCORE
|
||||
},
|
||||
hints: [],
|
||||
timeBetween: 0,
|
||||
|
||||
Reference in New Issue
Block a user