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
|
HOTJAR_DEBUG=false
|
||||||
INVITE_STUDENTS_EMAIL_TO=''
|
INVITE_STUDENTS_EMAIL_TO=''
|
||||||
ENABLE_CHECKLIST_QUALITY=''
|
ENABLE_CHECKLIST_QUALITY=''
|
||||||
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
|
|
||||||
# "Multi-level" blocks are unsupported in libraries
|
# "Multi-level" blocks are unsupported in libraries
|
||||||
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content,itembank"
|
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content,itembank"
|
||||||
# Fallback in local style files
|
# Fallback in local style files
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ HOTJAR_VERSION=6
|
|||||||
HOTJAR_DEBUG=true
|
HOTJAR_DEBUG=true
|
||||||
INVITE_STUDENTS_EMAIL_TO="someone@domain.com"
|
INVITE_STUDENTS_EMAIL_TO="someone@domain.com"
|
||||||
ENABLE_CHECKLIST_QUALITY=true
|
ENABLE_CHECKLIST_QUALITY=true
|
||||||
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
|
|
||||||
# "Multi-level" blocks are unsupported in libraries
|
# "Multi-level" blocks are unsupported in libraries
|
||||||
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content,itembank"
|
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content,itembank"
|
||||||
# Fallback in local style files
|
# Fallback in local style files
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ ENABLE_TAGGING_TAXONOMY_PAGES=true
|
|||||||
BBB_LEARN_MORE_URL=''
|
BBB_LEARN_MORE_URL=''
|
||||||
INVITE_STUDENTS_EMAIL_TO="someone@domain.com"
|
INVITE_STUDENTS_EMAIL_TO="someone@domain.com"
|
||||||
ENABLE_CHECKLIST_QUALITY=true
|
ENABLE_CHECKLIST_QUALITY=true
|
||||||
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
|
|
||||||
# "Multi-level" blocks are unsupported in libraries
|
# "Multi-level" blocks are unsupported in libraries
|
||||||
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content,itembank"
|
LIBRARY_UNSUPPORTED_BLOCKS="conditional,step-builder,problem-builder,library_content,itembank"
|
||||||
PARAGON_THEME_URLS=
|
PARAGON_THEME_URLS=
|
||||||
|
|||||||
@@ -173,12 +173,18 @@ export const scoringCardHooks = (scoring, updateSettings, defaultValue) => {
|
|||||||
updateSettings({ scoring: { ...scoring, weight } });
|
updateSettings({ scoring: { ...scoring, weight } });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGradingMethodChange = (event) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
updateSettings({ scoring: { ...scoring, gradingMethod: value } });
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attemptDisplayValue,
|
attemptDisplayValue,
|
||||||
handleUnlimitedChange,
|
handleUnlimitedChange,
|
||||||
handleMaxAttemptChange,
|
handleMaxAttemptChange,
|
||||||
handleOnChange,
|
handleOnChange,
|
||||||
handleWeightChange,
|
handleWeightChange,
|
||||||
|
handleGradingMethodChange,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ describe('Problem settings hooks', () => {
|
|||||||
unlimited: false,
|
unlimited: false,
|
||||||
number: 5,
|
number: 5,
|
||||||
},
|
},
|
||||||
|
gradingMethod: 'last_score',
|
||||||
};
|
};
|
||||||
const defaultValue = 1;
|
const defaultValue = 1;
|
||||||
test('test scoringCardHooks initializes display value when attempts.number is null', () => {
|
test('test scoringCardHooks initializes display value when attempts.number is null', () => {
|
||||||
@@ -269,6 +270,11 @@ describe('Problem settings hooks', () => {
|
|||||||
output.handleWeightChange({ target: { value } });
|
output.handleWeightChange({ target: { value } });
|
||||||
expect(updateSettings).toHaveBeenCalledWith({ scoring: { ...scoring, weight: parseFloat(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', () => {
|
describe('Show answer card hooks', () => {
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ SettingsWidget.propTypes = {
|
|||||||
showanswer: PropTypes.string,
|
showanswer: PropTypes.string,
|
||||||
showResetButton: PropTypes.bool,
|
showResetButton: PropTypes.bool,
|
||||||
rerandomize: PropTypes.string,
|
rerandomize: PropTypes.string,
|
||||||
|
gradingMethod: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
images: PropTypes.shape({}).isRequired,
|
images: PropTypes.shape({}).isRequired,
|
||||||
isLibrary: PropTypes.bool.isRequired,
|
isLibrary: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ describe('SettingsWidget', () => {
|
|||||||
maxAttempts: 2,
|
maxAttempts: 2,
|
||||||
showanswer: 'finished',
|
showanswer: 'finished',
|
||||||
showResetButton: false,
|
showResetButton: false,
|
||||||
|
gradingMethod: 'last_score',
|
||||||
},
|
},
|
||||||
images: {},
|
images: {},
|
||||||
isLibrary: false,
|
isLibrary: false,
|
||||||
|
|||||||
@@ -82,6 +82,16 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Points',
|
defaultMessage: 'Points',
|
||||||
description: 'Scoring weight input label',
|
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: {
|
unlimitedAttemptsSummary: {
|
||||||
id: 'authoring.problemeditor.settings.scoring.unlimited',
|
id: 'authoring.problemeditor.settings.scoring.unlimited',
|
||||||
defaultMessage: 'Unlimited attempts',
|
defaultMessage: 'Unlimited attempts',
|
||||||
@@ -107,6 +117,11 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Specify point weight and the number of answer attempts',
|
defaultMessage: 'Specify point weight and the number of answer attempts',
|
||||||
description: 'Descriptive text for scoring settings',
|
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: {
|
attemptsHint: {
|
||||||
id: 'authoring.problemeditor.settings.scoring.attempts.hint',
|
id: 'authoring.problemeditor.settings.scoring.attempts.hint',
|
||||||
defaultMessage: 'If a default value is not set in advanced settings, unlimited attempts are allowed',
|
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',
|
defaultMessage: 'If a value is not set, the problem is worth one point',
|
||||||
description: 'Summary text for scoring weight',
|
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: {
|
showAnswerSettingsTitle: {
|
||||||
id: 'authoring.problemeditor.settings.showAnswer.title',
|
id: 'authoring.problemeditor.settings.showAnswer.title',
|
||||||
defaultMessage: 'Show answer',
|
defaultMessage: 'Show answer',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { selectors } from '../../../../../../data/redux';
|
|||||||
import SettingsOption from '../SettingsOption';
|
import SettingsOption from '../SettingsOption';
|
||||||
import messages from '../messages';
|
import messages from '../messages';
|
||||||
import { scoringCardHooks } from '../hooks';
|
import { scoringCardHooks } from '../hooks';
|
||||||
|
import { GradingMethod, GradingMethodKeys } from '../../../../../../data/constants/problem';
|
||||||
|
|
||||||
const ScoringCard = ({
|
const ScoringCard = ({
|
||||||
scoring,
|
scoring,
|
||||||
@@ -23,28 +24,62 @@ const ScoringCard = ({
|
|||||||
handleUnlimitedChange,
|
handleUnlimitedChange,
|
||||||
handleMaxAttemptChange,
|
handleMaxAttemptChange,
|
||||||
handleWeightChange,
|
handleWeightChange,
|
||||||
|
handleGradingMethodChange,
|
||||||
handleOnChange,
|
handleOnChange,
|
||||||
attemptDisplayValue,
|
attemptDisplayValue,
|
||||||
} = scoringCardHooks(scoring, updateSettings, defaultValue);
|
} = scoringCardHooks(scoring, updateSettings, defaultValue);
|
||||||
|
|
||||||
const getScoringSummary = (weight, attempts, unlimited) => {
|
const getScoringSummary = (weight, attempts, unlimited, gradingMethod) => {
|
||||||
let summary = intl.formatMessage(messages.weightSummary, { weight });
|
let summary = intl.formatMessage(messages.weightSummary, { weight });
|
||||||
summary += ` ${String.fromCharCode(183)} `;
|
summary += ` ${String.fromCharCode(183)} `;
|
||||||
summary += unlimited
|
summary += unlimited
|
||||||
? intl.formatMessage(messages.unlimitedAttemptsSummary)
|
? intl.formatMessage(messages.unlimitedAttemptsSummary)
|
||||||
: intl.formatMessage(messages.attemptsSummary, { attempts: attempts || defaultValue });
|
: 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 summary;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsOption
|
<SettingsOption
|
||||||
title={intl.formatMessage(messages.scoringSettingsTitle)}
|
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"
|
className="scoringCard"
|
||||||
>
|
>
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<FormattedMessage {...messages.scoringSettingsLabel} />
|
<FormattedMessage {...messages.scoringSettingsLabelWithGradingMethod} />
|
||||||
</div>
|
</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.Group>
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
} from '@src/testUtils';
|
} from '@src/testUtils';
|
||||||
import ScoringCard from './ScoringCard';
|
import ScoringCard from './ScoringCard';
|
||||||
import { selectors } from '../../../../../../data/redux';
|
import { selectors } from '../../../../../../data/redux';
|
||||||
|
import { GradingMethodKeys } from '../../../../../../data/constants/problem';
|
||||||
|
|
||||||
const { app } = selectors;
|
const { app } = selectors;
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ describe('ScoringCard', () => {
|
|||||||
unlimited: false,
|
unlimited: false,
|
||||||
number: 5,
|
number: 5,
|
||||||
},
|
},
|
||||||
|
gradingMethod: GradingMethodKeys.LAST_SCORE,
|
||||||
updateSettings: jest.fn().mockName('args.updateSettings'),
|
updateSettings: jest.fn().mockName('args.updateSettings'),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -59,6 +61,17 @@ describe('ScoringCard', () => {
|
|||||||
expect(props.updateSettings).toHaveBeenCalled();
|
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', () => {
|
test('should call updateSettings when clicking attempts button', () => {
|
||||||
const scoringUnlimited = { ...scoring, attempts: { unlimited: true, number: 0 } };
|
const scoringUnlimited = { ...scoring, attempts: { unlimited: true, number: 0 } };
|
||||||
render(<ScoringCard {...props} scoring={scoringUnlimited} />);
|
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, 'number', 'max_attempts', stateSettings.scoring.attempts, defaultSettings?.maxAttempts, true);
|
||||||
settings = popuplateItem(settings, 'weight', 'weight', stateSettings.scoring);
|
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);
|
settings = popuplateItem(settings, 'on', 'showanswer', stateSettings.showAnswer, defaultSettings?.showanswer, true);
|
||||||
if (includes(numberOfAttemptsChoice, stateSettings.showAnswer.on)) {
|
if (includes(numberOfAttemptsChoice, stateSettings.showAnswer.on)) {
|
||||||
settings = popuplateItem(settings, 'afterAttempts', 'attempts_before_showanswer_button', stateSettings.showAnswer);
|
settings = popuplateItem(settings, 'afterAttempts', 'attempts_before_showanswer_button', stateSettings.showAnswer);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const parseScoringSettings = (metadata, defaultSettings) => {
|
|||||||
scoring = { ...scoring, attempts };
|
scoring = { ...scoring, attempts };
|
||||||
|
|
||||||
scoring = popuplateItem(scoring, 'weight', 'weight', metadata);
|
scoring = popuplateItem(scoring, 'weight', 'weight', metadata);
|
||||||
|
scoring = popuplateItem(scoring, 'grading_method', 'gradingMethod', metadata);
|
||||||
|
|
||||||
return scoring;
|
return scoring;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -364,6 +364,32 @@ export const RandomizationTypes = StrictDict({
|
|||||||
},
|
},
|
||||||
} as const);
|
} 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 RichTextProblems = [ProblemTypeKeys.SINGLESELECT, ProblemTypeKeys.MULTISELECT] as const;
|
||||||
|
|
||||||
export const settingsOlxAttributes = [
|
export const settingsOlxAttributes = [
|
||||||
@@ -374,6 +400,7 @@ export const settingsOlxAttributes = [
|
|||||||
'@_show_reset_button',
|
'@_show_reset_button',
|
||||||
'@_submission_wait_seconds',
|
'@_submission_wait_seconds',
|
||||||
'@_attempts_before_showanswer_button',
|
'@_attempts_before_showanswer_button',
|
||||||
|
'@_grading_method',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const ignoredOlxAttributes = [
|
export const ignoredOlxAttributes = [
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { has } from 'lodash';
|
|||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import { indexToLetterMap } from '../../../containers/ProblemEditor/data/OLXParser';
|
import { indexToLetterMap } from '../../../containers/ProblemEditor/data/OLXParser';
|
||||||
import { StrictDict } from '../../../utils';
|
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 { ToleranceTypes } from '../../../containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Tolerance/constants';
|
||||||
import type { EditorState } from '..';
|
import type { EditorState } from '..';
|
||||||
|
|
||||||
@@ -29,6 +29,7 @@ const initialState: EditorState['problem'] = {
|
|||||||
unlimited: true,
|
unlimited: true,
|
||||||
number: null,
|
number: null,
|
||||||
},
|
},
|
||||||
|
gradingMethod: GradingMethodKeys.LAST_SCORE
|
||||||
},
|
},
|
||||||
hints: [],
|
hints: [],
|
||||||
timeBetween: 0,
|
timeBetween: 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user