Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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 @@ -6,6 +6,7 @@ import {
Icon,
IconButton,
Form,
Spinner,
} from '@openedx/paragon';
import { FeedbackOutline, DeleteOutline } from '@openedx/paragon/icons';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
Expand All @@ -19,6 +20,7 @@ import * as hooks from './hooks';
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
import ExpandableTextArea from '../../../../../sharedComponents/ExpandableTextArea';
import { answerRangeFormatRegex } from '../../../data/OLXParser';
import { useValidateInputBlock } from '../../../data/apiHooks';

const AnswerOption = ({
answer,
Expand All @@ -32,7 +34,6 @@ const AnswerOption = ({
const isLibrary = useSelector(selectors.app.isLibrary);
const learningContextId = useSelector(selectors.app.learningContextId);
const blockId = useSelector(selectors.app.blockId);

const removeAnswer = hooks.removeAnswer({ answer, dispatch });
const setAnswer = hooks.setAnswer({ answer, hasSingleAnswer, dispatch });
const setAnswerTitle = hooks.setAnswerTitle({
Expand All @@ -44,6 +45,7 @@ const AnswerOption = ({
const setSelectedFeedback = hooks.setSelectedFeedback({ answer, hasSingleAnswer, dispatch });
const setUnselectedFeedback = hooks.setUnselectedFeedback({ answer, hasSingleAnswer, dispatch });
const { isFeedbackVisible, toggleFeedback } = hooks.useFeedback(answer);
const { data = { is_valid: true }, mutate, isPending } = useValidateInputBlock();

const staticRootUrl = isLibrary
? `${getConfig().STUDIO_BASE_URL}/library_assets/blocks/${blockId}/`
Expand Down Expand Up @@ -71,15 +73,29 @@ const AnswerOption = ({
}
if (problemType !== ProblemTypeKeys.NUMERIC || !answer.isAnswerRange) {
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={!data?.is_valid ?? true}>
<Form.Control
as="textarea"
className="answer-option-textarea text-gray-500 small"
autoResize
rows={1}
value={answer.title}
onChange={(e) => {
setAnswerTitle(e);
mutate(e.target.value);
}}
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}

/>
{(!data?.is_valid ?? true) && (
<Form.Control.Feedback type="invalid">
<FormattedMessage {...messages.answerNumericErrorText} />
</Form.Control.Feedback>
)}
{isPending && (
<Spinner animation="border" className="mie-3 mt-3" screenReaderText="loading" />
)}
</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;
19 changes: 19 additions & 0 deletions src/editors/containers/ProblemEditor/data/apiHooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useMutation } from '@tanstack/react-query';
import { getConfig } from '@edx/frontend-platform';
import api from '@src/editors/data/services/cms/api';

const getApiBaseUrl = () => getConfig().STUDIO_BASE_URL;

export const useValidateInputBlock = () => useMutation({
Comment thread
bradenmacdonald marked this conversation as resolved.
mutationFn: async (title) => {
try {
const res = await api.validateBlockNumericInput({ studioEndpointUrl: `${getApiBaseUrl()}`, data: { formula: title } });
return res.data;
Comment thread
bradenmacdonald marked this conversation as resolved.
Outdated
} catch (err: any) {
return {
is_valid: false,
error: err.response?.data?.error ?? 'Unknown error',
};
}
},
});
Binary file modified src/editors/data/images/numericalInput.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/editors/data/services/cms/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,13 @@ export const apiMethods = {
}) => get(
urls.handlerUrl({ studioEndpointUrl, blockId, handlerName }),
),
validateBlockNumericInput: ({
studioEndpointUrl,
data,
}) => post(
urls.validateNumericInputUrl({ studioEndpointUrl }),
data,
),
};

export default apiMethods;
4 changes: 4 additions & 0 deletions src/editors/data/services/cms/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,7 @@ export const courseVideos = (({ studioEndpointUrl, learningContextId }) => (
export const handlerUrl = (({ studioEndpointUrl, blockId, handlerName }) => (
`${studioEndpointUrl}/api/xblock/v2/xblocks/${blockId}/handler_url/${handlerName}/`
)) satisfies UrlFunction;

export const validateNumericInputUrl = (({ studioEndpointUrl }) => (
`${studioEndpointUrl}/api/contentstore/v2/validate/numerical-input/`
)) satisfies UrlFunction;