Files
frontend-app-authoring/src/editors/data/redux/thunkActions/problem.ts
Braden MacDonald 314dfa60e2 feat: Enable capa problem editor for components in libraries (#1290)
* feat: enable the problem editor for library components

* fix: don't try to load "advanced settings" when editing problem in library

* fix: don't fetch images when editing problem in library

* docs: add a note about plans for the editor modal

* fix: choosing a problem type then cancelling resulted in an error

* chore: remove unused mockApi, clean up problematic 'module' self import

* test: update workflow test to test problem editor

* feat: show capa content summary on cards in library search results

* docs: fix comment typos found in code review

* refactor: add 'key-utils' to consolidate opaque key logic
2024-09-18 17:45:41 +00:00

103 lines
4.7 KiB
TypeScript

import _ from 'lodash';
import { actions as problemActions } from '../problem';
import { actions as requestActions } from '../requests';
import { selectors as appSelectors } from '../app';
import * as requests from './requests';
import { isLibraryKey } from '../../../../generic/key-utils';
import { OLXParser } from '../../../containers/ProblemEditor/data/OLXParser';
import { parseSettings } from '../../../containers/ProblemEditor/data/SettingsParser';
import { ProblemTypeKeys } from '../../constants/problem';
import ReactStateOLXParser from '../../../containers/ProblemEditor/data/ReactStateOLXParser';
import { camelizeKeys } from '../../../utils';
import { fetchEditorContent } from '../../../containers/ProblemEditor/components/EditProblemView/hooks';
import { RequestKeys } from '../../constants/requests';
// Similar to `import { actions, selectors } from '..';` but avoid circular imports:
const actions = { problem: problemActions, requests: requestActions };
const selectors = { app: appSelectors };
export const switchToAdvancedEditor = () => (dispatch, getState) => {
const state = getState();
const editorObject = fetchEditorContent({ format: '' });
const reactOLXParser = new ReactStateOLXParser({ problem: state.problem, editorObject });
const rawOLX = reactOLXParser.buildOLX();
dispatch(actions.problem.updateField({ problemType: ProblemTypeKeys.ADVANCED, rawOLX }));
};
export const isBlankProblem = ({ rawOLX }) => {
if (['<problem></problem>', '<problem/>'].includes(rawOLX.replace(/\s/g, ''))) {
return true;
}
return false;
};
export const getDataFromOlx = ({ rawOLX, rawSettings, defaultSettings }) => {
let olxParser;
let parsedProblem;
const { default_to_advanced: defaultToAdvanced } = rawSettings;
try {
olxParser = new OLXParser(rawOLX);
if (defaultToAdvanced) {
parsedProblem = olxParser.getBetaParsedOLXData();
} else {
parsedProblem = olxParser.getParsedOLXData();
}
} catch (error) {
// eslint-disable-next-line no-console
console.error('The Problem Could Not Be Parsed from OLX. redirecting to Advanced editor.', error);
return { problemType: ProblemTypeKeys.ADVANCED, rawOLX, settings: parseSettings(rawSettings, defaultSettings) };
}
if (parsedProblem?.problemType === ProblemTypeKeys.ADVANCED) {
return { problemType: ProblemTypeKeys.ADVANCED, rawOLX, settings: parseSettings(rawSettings, defaultSettings) };
}
const { settings, ...data } = parsedProblem;
const parsedSettings = { ...settings, ...parseSettings(rawSettings, defaultSettings) };
if (!_.isEmpty(rawOLX) && !_.isEmpty(data)) {
return { ...data, rawOLX, settings: parsedSettings };
}
return { settings: parsedSettings };
};
export const loadProblem = ({ rawOLX, rawSettings, defaultSettings }) => (dispatch) => {
if (isBlankProblem({ rawOLX })) {
dispatch(actions.problem.setEnableTypeSelection(camelizeKeys(defaultSettings)));
} else {
dispatch(actions.problem.load(getDataFromOlx({ rawOLX, rawSettings, defaultSettings })));
}
};
export const fetchAdvancedSettings = ({ rawOLX, rawSettings }) => (dispatch) => {
const advancedProblemSettingKeys = ['max_attempts', 'showanswer', 'show_reset_button', 'rerandomize'];
dispatch(requests.fetchAdvancedSettings({
onSuccess: (response) => {
const defaultSettings = {};
Object.entries(response.data as Record<string, any>).forEach(([key, value]) => {
if (advancedProblemSettingKeys.includes(key)) {
defaultSettings[key] = value.value;
}
});
dispatch(actions.problem.updateField({ defaultSettings: camelizeKeys(defaultSettings) }));
loadProblem({ rawOLX, rawSettings, defaultSettings })(dispatch);
},
onFailure: () => { loadProblem({ rawOLX, rawSettings, defaultSettings: {} })(dispatch); },
}));
};
export const initializeProblem = (blockValue) => (dispatch, getState) => {
const rawOLX = _.get(blockValue, 'data.data', {});
const rawSettings = _.get(blockValue, 'data.metadata', {});
const learningContextId = selectors.app.learningContextId(getState());
if (isLibraryKey(learningContextId)) {
// Content libraries don't yet support defaults for fields like max_attempts, showanswer, etc.
// So proceed with loading the problem.
// Though first we need to fake the request or else the problem type selection UI won't display:
dispatch(actions.requests.completeRequest({ requestKey: RequestKeys.fetchAdvancedSettings, response: {} }));
dispatch(loadProblem({ rawOLX, rawSettings, defaultSettings: {} }));
} else {
// Load the defaults (for max_attempts, etc.) from the course's advanced settings, then proceed:
dispatch(fetchAdvancedSettings({ rawOLX, rawSettings }));
}
};
export default { initializeProblem, switchToAdvancedEditor, fetchAdvancedSettings };