Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { FeedbackBox } from './components/Feedback';
import * as hooks from './hooks';
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
import ExpandableTextArea from '../../../../../sharedComponents/ExpandableTextArea';
import { answerRangeFormatRegex } from '../../../data/OLXParser';
import { answerRangeFormatRegex, numericRegex } from '../../../data/OLXParser';

const AnswerOption = ({
answer,
Expand Down Expand Up @@ -53,6 +53,10 @@ const AnswerOption = ({
const cleanedValue = value.replace(/^\s+|\s+$/g, '');
return !cleanedValue.length || answerRangeFormatRegex.test(cleanedValue);
};
const validateAnswerNumeric = (value) => {
const cleanedValue = (value ?? '').trim();
return !cleanedValue.length || numericRegex.test(cleanedValue);
};

const getInputArea = () => {
if ([ProblemTypeKeys.SINGLESELECT, ProblemTypeKeys.MULTISELECT].includes(problemType)) {
Expand All @@ -70,16 +74,25 @@ const AnswerOption = ({
);
}
if (problemType !== ProblemTypeKeys.NUMERIC || !answer.isAnswerRange) {
const isValidValue = validateAnswerNumeric(answer.title);
return (
<Form.Control
as="textarea"
className="answer-option-textarea text-gray-500 small"
autoResize
rows={1}
value={answer.title}
onChange={setAnswerTitle}
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}
/>
<Form.Group isInvalid={!isValidValue}>
<Form.Control
as="textarea"
className="answer-option-textarea text-gray-500 small"
autoResize
rows={1}
value={answer.title}
onChange={setAnswerTitle}
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}

/>
{!isValidValue && (
<Form.Control.Feedback type="invalid">
<FormattedMessage {...messages.answerNumericErrorText} />
</Form.Control.Feedback>
)}
</Form.Group>
);
}
// Return Answer Range View
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ const messages = defineMessages({
defaultMessage: 'Error: Invalid range format. Use brackets or parentheses with values separated by a comma.',
description: 'Error text describing wrong format of answer ranges',
},
answerNumericErrorText: {
id: 'authoring.answerwidget.answer.answerNumericErrorText',
defaultMessage: 'Error: This input type only supports numeric answers. Did you mean to make a Text input or Math expression input problem?',
description: 'Error message when user provides wrong format',
},
});

export default messages;
2 changes: 1 addition & 1 deletion src/editors/containers/ProblemEditor/data/OLXParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const responseKeys = [
* []
*/
export const answerRangeFormatRegex = /^[([]\s*-?(?:\d+(?:\.\d+)?|\d+\/\d+)\s*,\s*-?(?:\d+(?:\.\d+)?|\d+\/\d+)\s*[)\]]$/m;

export const numericRegex = /^[+-]?(\d+(\.\d*)?|\.\d+)$/;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Numeric input problems can also include sin, pi, and a few other functions.

I would ask you to reference the docs (https://docs.openedx.org/en/latest/educators/references/course_development/exercise_tools/numerical_input.html), but those docs aren't entirely correct either 🤦🏻 In particular, the docs say that g is allows, but this does not work in practice.

I'll have to do some digging in the code to figure out what's really allowed; I'll circle back. In the meantime, can you try updating the regex to include pi, sqrt, sin, arcscin, cos, arccos, tan, arctan, log2, and parenthases ( ) ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, here's the function which perform the numeric evaluation: https://github.com/openedx/openedx-calc/blob/master/calc/calc.py#L239

And here's the call site: https://github.com/openedx/edx-platform/blob/f4f14a69874d5804ef33778486f7d6cab400e206/xmodule/capa/responsetypes.py#L1563

Note that both variables and unary_functions are passed in as {}.

@jesusbalderramawgu , would you or your team be able to reverse engineer this function in order to figure out exactly what is allowed in a numeric input formula?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, I will check this with the team

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello, just to follow up on the ticket, I'm still checking because now needs more work the validation part.
After this feedback, the frontend shouldn't validate if is a valid math expression, it should be on the backend. After some review I found an endpoint to do this validation but it's really attached to django views, so I'm looking for a way of request this from react and validate the input component.
I'll keep you posted, thanks in advance!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also thinking that the validation might need to happen on the backend, glad to see you came to the same conclusion.

Perhaps we need to factor out a new REST API for the validation? Curious to hear what you find.

Copy link
Author

@jesusbalderramawgu jesusbalderramawgu Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree on the new rest API for this validation, but gonna check with the team first in case they know something about how to reuse it.

this is where the validation is handled inside edx platform
xmodule/capa/inputtypes.py

in this function, line 1277
def preview_formcalc(self, get):

it handles every formula as latex

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a solution, I would like your thoughts on this.
after some digging and checking with @holaontiveros
we found that is possible to request the validation endpoint in this view, but it is required to save the numeric input first.

Basically when we select "problem" in the options and the modal opens up
a block is created with this endpoint
http://studio.local.openedx.io:8001/xblock/
returning a blockId which is required to validate the formula, but the input is not created because is not saved yet.
so we can't validate the formula without the creation of the input because even though we have a blockId it is like a temporary id until we create the input. We need to save the input first to get a valid id and be able to validate.

my proposal is to save an empty response (numerical input) when the modal opens up, that way we would have a valid id as reference and when the user types in the answer we can validate successfully.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @jesusbalderramawgu , thanks for looking into this. That particular approach is not viable, because we need the editor to be able to work without saving the Problem first. You can see the importance of this by going into Libraries (Beta), creating a Problem, and hitting "Cancel" instead of saving: notice that no Problem is created.

(In Courses, on the other hand, if you being creating a Problem and hit "Cancel", you will end up with a blank problem which you have to then delete. This is an undesirable legacy behavior which we'll want to fix one day.)

I think we'll need a new endpoint, maybe one that just takes an answer string and validates it for a particular response type (formula, numeric, etc.)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for your feedback @kdmccormick, I checked what you said about the flow and I agree.
a new endpoint is required for this then, it should receive a string and return a boolean to know if is valid or not.
do you know who can help with the creation of this endpoint?

export const stripNonTextTags = ({ input, tag }) => {
const stripedTags = {};
Object.entries(input).forEach(([key, value]) => {
Expand Down