feat: lighter build by rewriting lodash imports (#1772)
Incorrect lodash imports are causing MFEs to import the entire lodash library. This change shaves off a few kB of the compressed build.
This commit is contained in:
@@ -20,7 +20,7 @@ import {
|
||||
Cached, CheckCircle, Launch, Loop,
|
||||
} from '@openedx/paragon/icons';
|
||||
|
||||
import _ from 'lodash';
|
||||
import sumBy from 'lodash/sumBy';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import getPageHeadTitle from '../generic/utils';
|
||||
import { useModel } from '../generic/model-store';
|
||||
@@ -109,7 +109,7 @@ export const CourseLibraries: React.FC<Props> = ({ courseId }) => {
|
||||
);
|
||||
const [showReviewAlert, setShowReviewAlert] = useState(false);
|
||||
const { data: libraries, isLoading } = useEntityLinksSummaryByDownstreamContext(courseId);
|
||||
const outOfSyncCount = useMemo(() => _.sumBy(libraries, (lib) => lib.readyToSyncCount), [libraries]);
|
||||
const outOfSyncCount = useMemo(() => sumBy(libraries, (lib) => lib.readyToSyncCount), [libraries]);
|
||||
const {
|
||||
isLoadingPage: isLoadingStudioHome,
|
||||
isFailedLoadingPage: isFailedLoadingStudioHome,
|
||||
|
||||
@@ -14,7 +14,9 @@ import {
|
||||
useToggle,
|
||||
} from '@openedx/paragon';
|
||||
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
tail, keyBy, orderBy, merge, omitBy,
|
||||
} from 'lodash';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { Loop, Warning } from '@openedx/paragon/icons';
|
||||
import messages from './messages';
|
||||
@@ -49,7 +51,7 @@ interface BlockCardProps {
|
||||
const BlockCard: React.FC<BlockCardProps> = ({ info, actions }) => {
|
||||
const intl = useIntl();
|
||||
const componentIcon = getItemIcon(info.blockType);
|
||||
const breadcrumbs = _.tail(info.breadcrumbs) as Array<{ displayName: string, usageKey: string }>;
|
||||
const breadcrumbs = tail(info.breadcrumbs) as Array<{ displayName: string, usageKey: string }>;
|
||||
|
||||
const getBlockLink = useCallback(() => {
|
||||
let key = info.usageKey;
|
||||
@@ -138,11 +140,11 @@ const ComponentReviewList = ({
|
||||
);
|
||||
|
||||
const outOfSyncComponentsByKey = useMemo(
|
||||
() => _.keyBy(outOfSyncComponents, 'downstreamUsageKey'),
|
||||
() => keyBy(outOfSyncComponents, 'downstreamUsageKey'),
|
||||
[outOfSyncComponents],
|
||||
);
|
||||
const downstreamInfoByKey = useMemo(
|
||||
() => _.keyBy(downstreamInfo, 'usageKey'),
|
||||
() => keyBy(downstreamInfo, 'usageKey'),
|
||||
[downstreamInfo],
|
||||
);
|
||||
const queryClient = useQueryClient();
|
||||
@@ -241,9 +243,9 @@ const ComponentReviewList = ({
|
||||
if (isIndexDataLoading) {
|
||||
return [];
|
||||
}
|
||||
let merged = _.merge(downstreamInfoByKey, outOfSyncComponentsByKey);
|
||||
merged = _.omitBy(merged, (o) => !o.displayName);
|
||||
const ordered = _.orderBy(Object.values(merged), 'updated', 'desc');
|
||||
let merged = merge(downstreamInfoByKey, outOfSyncComponentsByKey);
|
||||
merged = omitBy(merged, (o) => !o.displayName);
|
||||
const ordered = orderBy(Object.values(merged), 'updated', 'desc');
|
||||
return ordered;
|
||||
}, [downstreamInfoByKey, outOfSyncComponentsByKey]);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
includes, isEmpty, isFinite, isNaN, isNil,
|
||||
} from 'lodash';
|
||||
// This 'module' self-import hack enables mocking during tests.
|
||||
// See src/editors/decisions/0005-internal-editor-testability-decisions.md. The whole approach to how hooks are tested
|
||||
// should be re-thought and cleaned up to avoid this pattern.
|
||||
@@ -65,7 +67,7 @@ export const hintsCardHooks = (hints, updateSettings) => {
|
||||
|
||||
const handleAdd = () => {
|
||||
let newId = 0;
|
||||
if (!_.isEmpty(hints)) {
|
||||
if (!isEmpty(hints)) {
|
||||
newId = Math.max(...hints.map(hint => hint.id)) + 1;
|
||||
}
|
||||
const hint = { id: newId, value: '' };
|
||||
@@ -114,9 +116,9 @@ export const resetCardHooks = (updateSettings) => {
|
||||
|
||||
export const scoringCardHooks = (scoring, updateSettings, defaultValue) => {
|
||||
let loadedAttemptsNumber = scoring.attempts.number;
|
||||
if ((loadedAttemptsNumber === defaultValue || !_.isFinite(loadedAttemptsNumber)) && _.isFinite(defaultValue)) {
|
||||
if ((loadedAttemptsNumber === defaultValue || !isFinite(loadedAttemptsNumber)) && isFinite(defaultValue)) {
|
||||
loadedAttemptsNumber = `${defaultValue} (Default)`;
|
||||
} else if (loadedAttemptsNumber === defaultValue && _.isNil(defaultValue)) {
|
||||
} else if (loadedAttemptsNumber === defaultValue && isNil(defaultValue)) {
|
||||
loadedAttemptsNumber = '';
|
||||
}
|
||||
const [attemptDisplayValue, setAttemptDisplayValue] = module.state.attemptDisplayValue(loadedAttemptsNumber);
|
||||
@@ -135,9 +137,9 @@ export const scoringCardHooks = (scoring, updateSettings, defaultValue) => {
|
||||
let unlimitedAttempts = false;
|
||||
let attemptNumber = parseInt(event.target.value, 10);
|
||||
|
||||
if (!_.isFinite(attemptNumber) || attemptNumber === defaultValue) {
|
||||
if (!isFinite(attemptNumber) || attemptNumber === defaultValue) {
|
||||
attemptNumber = null;
|
||||
if (_.isFinite(defaultValue)) {
|
||||
if (isFinite(defaultValue)) {
|
||||
setAttemptDisplayValue(`${defaultValue} (Default)`);
|
||||
} else {
|
||||
setAttemptDisplayValue('');
|
||||
@@ -154,7 +156,7 @@ export const scoringCardHooks = (scoring, updateSettings, defaultValue) => {
|
||||
let newMaxAttempt = parseInt(event.target.value, 10);
|
||||
if (newMaxAttempt === defaultValue) {
|
||||
newMaxAttempt = `${defaultValue} (Default)`;
|
||||
} else if (_.isNaN(newMaxAttempt)) {
|
||||
} else if (isNaN(newMaxAttempt)) {
|
||||
newMaxAttempt = '';
|
||||
} else if (newMaxAttempt < 0) {
|
||||
newMaxAttempt = 0;
|
||||
@@ -164,7 +166,7 @@ export const scoringCardHooks = (scoring, updateSettings, defaultValue) => {
|
||||
|
||||
const handleWeightChange = (event) => {
|
||||
let weight = parseFloat(event.target.value);
|
||||
if (_.isNaN(weight) || weight < 0) {
|
||||
if (isNaN(weight) || weight < 0) {
|
||||
weight = 0;
|
||||
}
|
||||
updateSettings({ scoring: { ...scoring, weight } });
|
||||
@@ -187,18 +189,18 @@ export const useAnswerSettings = (showAnswer, updateSettings) => {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
setShowAttempts(_.includes(numberOfAttemptsChoice, showAnswer.on));
|
||||
setShowAttempts(includes(numberOfAttemptsChoice, showAnswer.on));
|
||||
}, [showAttempts]);
|
||||
|
||||
const handleShowAnswerChange = (event) => {
|
||||
const { value } = event.target;
|
||||
setShowAttempts(_.includes(numberOfAttemptsChoice, value));
|
||||
setShowAttempts(includes(numberOfAttemptsChoice, value));
|
||||
updateSettings({ showAnswer: { ...showAnswer, on: value } });
|
||||
};
|
||||
|
||||
const handleAttemptsChange = (event) => {
|
||||
let attempts = parseInt(event.target.value, 10);
|
||||
if (_.isNaN(attempts) || attempts < 0) {
|
||||
if (isNaN(attempts) || attempts < 0) {
|
||||
attempts = 0;
|
||||
}
|
||||
updateSettings({ showAnswer: { ...showAnswer, afterAttempts: attempts } });
|
||||
@@ -214,7 +216,7 @@ export const useAnswerSettings = (showAnswer, updateSettings) => {
|
||||
export const timerCardHooks = (updateSettings) => ({
|
||||
handleChange: (event) => {
|
||||
let time = parseInt(event.target.value, 10);
|
||||
if (_.isNaN(time) || time < 0) {
|
||||
if (isNaN(time) || time < 0) {
|
||||
time = 0;
|
||||
}
|
||||
updateSettings({ timeBetween: time });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import _ from 'lodash';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import messages from './messages';
|
||||
// This 'module' self-import hack enables mocking during tests.
|
||||
// See src/editors/decisions/0005-internal-editor-testability-decisions.md. The whole approach to how hooks are tested
|
||||
@@ -19,7 +19,7 @@ export const generalFeedbackHooks = (generalFeedback, updateSettings) => {
|
||||
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
useEffect(() => {
|
||||
if (_.isEmpty(generalFeedback)) {
|
||||
if (isEmpty(generalFeedback)) {
|
||||
setSummary({ message: messages.noGeneralFeedbackSummary, values: {}, intl: true });
|
||||
} else {
|
||||
setSummary({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import isNil from 'lodash/isNil';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
@@ -76,7 +76,7 @@ const ScoringCard = ({
|
||||
className="mt-3 decoration-control-label"
|
||||
checked={scoring.attempts.unlimited}
|
||||
onChange={handleUnlimitedChange}
|
||||
disabled={!_.isNil(defaultValue)}
|
||||
disabled={!isNil(defaultValue)}
|
||||
>
|
||||
<div className="x-small">
|
||||
<FormattedMessage {...messages.unlimitedAttemptsCheckboxLabel} />
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
/* eslint no-eval: 0 */
|
||||
|
||||
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
get, has, keys, isArray, isEmpty,
|
||||
} from 'lodash';
|
||||
import {
|
||||
ProblemTypeKeys,
|
||||
RichTextProblems,
|
||||
@@ -92,7 +94,7 @@ export class OLXParser {
|
||||
const parser = new XMLParser(parserOptions);
|
||||
this.builder = new XMLBuilder(builderOptions);
|
||||
this.parsedOLX = parser.parse(olxString);
|
||||
if (_.has(this.parsedOLX, 'problem')) {
|
||||
if (has(this.parsedOLX, 'problem')) {
|
||||
this.problem = this.parsedOLX.problem;
|
||||
}
|
||||
|
||||
@@ -112,7 +114,7 @@ export class OLXParser {
|
||||
const richTextParser = new XMLParser(richTextOptions);
|
||||
this.richTextBuilder = new XMLBuilder(richTextBuilderOptions);
|
||||
this.richTextOLX = richTextParser.parse(olxString);
|
||||
if (_.has(this.parsedOLX, 'problem')) {
|
||||
if (has(this.parsedOLX, 'problem')) {
|
||||
this.richTextProblem = this.richTextOLX[0].problem;
|
||||
}
|
||||
}
|
||||
@@ -188,17 +190,17 @@ export class OLXParser {
|
||||
);
|
||||
const answers = [];
|
||||
let data = {};
|
||||
const widget = _.get(this.problem, `${problemType}.${widgetName}`);
|
||||
const widget = get(this.problem, `${problemType}.${widgetName}`);
|
||||
const permissableTags = ['choice', '@_type', 'compoundhint', 'option', '#text'];
|
||||
if (_.keys(widget).some((tag) => !permissableTags.includes(tag))) {
|
||||
if (keys(widget).some((tag) => !permissableTags.includes(tag))) {
|
||||
throw new Error('Misc Tags, reverting to Advanced Editor');
|
||||
}
|
||||
if (_.get(this.problem, `${problemType}.@_partial_credit`)) {
|
||||
if (get(this.problem, `${problemType}.@_partial_credit`)) {
|
||||
throw new Error('Partial credit not supported by GUI, reverting to Advanced Editor');
|
||||
}
|
||||
const choice = _.get(widget, option);
|
||||
const choice = get(widget, option);
|
||||
const isComplexAnswer = RichTextProblems.includes(problemType);
|
||||
if (_.isEmpty(choice)) {
|
||||
if (isEmpty(choice)) {
|
||||
answers.push(
|
||||
{
|
||||
id: indexToLetterMap[answers.length],
|
||||
@@ -206,7 +208,7 @@ export class OLXParser {
|
||||
correct: true,
|
||||
},
|
||||
);
|
||||
} else if (_.isArray(choice)) {
|
||||
} else if (isArray(choice)) {
|
||||
choice.forEach((element, index) => {
|
||||
const preservedAnswer = preservedAnswers[index].filter(answer => !Object.keys(answer).includes(`${option}hint`));
|
||||
const preservedFeedback = preservedAnswers[index].filter(answer => Object.keys(answer).includes(`${option}hint`));
|
||||
@@ -265,11 +267,11 @@ export class OLXParser {
|
||||
getAnswerFeedback(preservedFeedback, hintKey) {
|
||||
const feedback = {};
|
||||
let feedbackKeys = 'selectedFeedback';
|
||||
if (_.isEmpty(preservedFeedback)) { return feedback; }
|
||||
if (isEmpty(preservedFeedback)) { return feedback; }
|
||||
|
||||
preservedFeedback.forEach((feedbackArr) => {
|
||||
if (_.has(feedbackArr, hintKey)) {
|
||||
if (_.has(feedbackArr, ':@') && _.has(feedbackArr[':@'], '@_selected')) {
|
||||
if (has(feedbackArr, hintKey)) {
|
||||
if (has(feedbackArr, ':@') && has(feedbackArr[':@'], '@_selected')) {
|
||||
const isSelectedFeedback = feedbackArr[':@']['@_selected'] === 'true';
|
||||
feedbackKeys = isSelectedFeedback ? 'selectedFeedback' : 'unselectedFeedback';
|
||||
}
|
||||
@@ -287,9 +289,9 @@ export class OLXParser {
|
||||
*/
|
||||
getGroupedFeedback(choices) {
|
||||
const groupFeedback = [];
|
||||
if (_.has(choices, 'compoundhint')) {
|
||||
if (has(choices, 'compoundhint')) {
|
||||
const groupFeedbackArray = choices.compoundhint;
|
||||
if (_.isArray(groupFeedbackArray)) {
|
||||
if (isArray(groupFeedbackArray)) {
|
||||
groupFeedbackArray.forEach((element) => {
|
||||
const parsedFeedback = stripNonTextTags({ input: element, tag: '@_value' });
|
||||
groupFeedback.push({
|
||||
@@ -338,12 +340,12 @@ export class OLXParser {
|
||||
selectedFeedback: firstFeedback,
|
||||
});
|
||||
|
||||
const additionalAnswerFeedback = preservedFeedback.filter(feedback => _.isArray(feedback));
|
||||
const stringEqualHintFeedback = preservedFeedback.filter(feedback => !_.isArray(feedback));
|
||||
const additionalAnswerFeedback = preservedFeedback.filter(feedback => isArray(feedback));
|
||||
const stringEqualHintFeedback = preservedFeedback.filter(feedback => !isArray(feedback));
|
||||
|
||||
// Parsing additional_answer for string response.
|
||||
const additionalAnswer = _.get(stringresponse, 'additional_answer', []);
|
||||
if (_.isArray(additionalAnswer)) {
|
||||
const additionalAnswer = get(stringresponse, 'additional_answer', []);
|
||||
if (isArray(additionalAnswer)) {
|
||||
additionalAnswer.forEach((newAnswer, indx) => {
|
||||
answerFeedback = this.getFeedback(additionalAnswerFeedback[indx]);
|
||||
answers.push({
|
||||
@@ -364,8 +366,8 @@ export class OLXParser {
|
||||
}
|
||||
|
||||
// Parsing stringequalhint for string response.
|
||||
const stringEqualHint = _.get(stringresponse, 'stringequalhint', []);
|
||||
if (_.isArray(stringEqualHint)) {
|
||||
const stringEqualHint = get(stringresponse, 'stringequalhint', []);
|
||||
if (isArray(stringEqualHint)) {
|
||||
stringEqualHint.forEach((newAnswer, indx) => {
|
||||
answerFeedback = this.getFeedback(stringEqualHintFeedback[indx]?.stringequalhint);
|
||||
answers.push({
|
||||
@@ -387,9 +389,9 @@ export class OLXParser {
|
||||
|
||||
// TODO: Support multiple types.
|
||||
additionalStringAttributes = {
|
||||
type: _.get(stringresponse, '@_type'),
|
||||
type: get(stringresponse, '@_type'),
|
||||
textline: {
|
||||
size: _.get(stringresponse, 'textline.@_size'),
|
||||
size: get(stringresponse, 'textline.@_size'),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -416,16 +418,16 @@ export class OLXParser {
|
||||
'correcthint',
|
||||
);
|
||||
const { numericalresponse } = this.problem;
|
||||
if (_.get(numericalresponse, '@_partial_credit')) {
|
||||
if (get(numericalresponse, '@_partial_credit')) {
|
||||
throw new Error('Partial credit not supported by GUI, reverting to Advanced Editor');
|
||||
}
|
||||
let answerFeedback = '';
|
||||
const answers = [];
|
||||
let responseParam = {};
|
||||
const feedback = this.getFeedback(firstCorrectFeedback);
|
||||
if (_.has(numericalresponse, 'responseparam')) {
|
||||
const type = _.get(numericalresponse, 'responseparam.@_type');
|
||||
const defaultValue = _.get(numericalresponse, 'responseparam.@_default');
|
||||
if (has(numericalresponse, 'responseparam')) {
|
||||
const type = get(numericalresponse, 'responseparam.@_type');
|
||||
const defaultValue = get(numericalresponse, 'responseparam.@_default');
|
||||
responseParam = {
|
||||
[type]: defaultValue,
|
||||
};
|
||||
@@ -441,8 +443,8 @@ export class OLXParser {
|
||||
});
|
||||
|
||||
// Parsing additional_answer for numerical response.
|
||||
const additionalAnswer = _.get(numericalresponse, 'additional_answer', []);
|
||||
if (_.isArray(additionalAnswer)) {
|
||||
const additionalAnswer = get(numericalresponse, 'additional_answer', []);
|
||||
if (isArray(additionalAnswer)) {
|
||||
additionalAnswer.forEach((newAnswer, indx) => {
|
||||
answerFeedback = this.getFeedback(preservedFeedback[indx]);
|
||||
answers.push({
|
||||
@@ -475,7 +477,7 @@ export class OLXParser {
|
||||
* @return {string} string of OLX
|
||||
*/
|
||||
parseQuestions(problemType) {
|
||||
const problemArray = _.get(this.richTextProblem[0], problemType) || this.richTextProblem;
|
||||
const problemArray = get(this.richTextProblem[0], problemType) || this.richTextProblem;
|
||||
|
||||
const questionArray = [];
|
||||
problemArray.forEach(tag => {
|
||||
@@ -548,7 +550,7 @@ export class OLXParser {
|
||||
*/
|
||||
getHints() {
|
||||
const hintsObject = [];
|
||||
if (_.has(this.problem, 'demandhint.hint')) {
|
||||
if (has(this.problem, 'demandhint.hint')) {
|
||||
const preservedProblem = this.richTextProblem;
|
||||
preservedProblem.forEach(obj => {
|
||||
const objKeys = Object.keys(obj);
|
||||
@@ -578,21 +580,21 @@ export class OLXParser {
|
||||
* @return {string} string of OLX
|
||||
*/
|
||||
getSolutionExplanation(problemType) {
|
||||
if (!_.has(this.problem, `${problemType}.solution`) && !_.has(this.problem, 'solution')) { return null; }
|
||||
if (!has(this.problem, `${problemType}.solution`) && !has(this.problem, 'solution')) { return null; }
|
||||
const [problemBody] = this.richTextProblem.filter(section => Object.keys(section).includes(problemType));
|
||||
const [solutionBody] = problemBody[problemType].filter(section => Object.keys(section).includes('solution'));
|
||||
const [divBody] = solutionBody.solution.filter(section => Object.keys(section).includes('div'));
|
||||
const solutionArray = [];
|
||||
if (divBody && divBody.div) {
|
||||
divBody.div.forEach(tag => {
|
||||
const tagText = _.get(Object.values(tag)[0][0], '#text', '');
|
||||
const tagText = get(Object.values(tag)[0][0], '#text', '');
|
||||
if (tagText.toString().trim() !== 'Explanation') {
|
||||
solutionArray.push(tag);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
solutionBody.solution.forEach(tag => {
|
||||
const tagText = _.get(Object.values(tag)[0][0], '#text', '');
|
||||
const tagText = get(Object.values(tag)[0][0], '#text', '');
|
||||
if (tagText.toString().trim() !== 'Explanation') {
|
||||
solutionArray.push(tag);
|
||||
}
|
||||
@@ -610,7 +612,7 @@ export class OLXParser {
|
||||
* @return {string} string of feedback
|
||||
*/
|
||||
getFeedback(xmlElement) {
|
||||
if (_.isEmpty(xmlElement)) { return ''; }
|
||||
if (isEmpty(xmlElement)) { return ''; }
|
||||
const feedbackString = this.richTextBuilder.build(xmlElement);
|
||||
return feedbackString;
|
||||
}
|
||||
@@ -636,7 +638,7 @@ export class OLXParser {
|
||||
}
|
||||
// make sure compound problems are treated as advanced
|
||||
if ((problemTypeKeys.length > 1)
|
||||
|| (_.isArray(this.problem[problemTypeKeys[0]])
|
||||
|| (isArray(this.problem[problemTypeKeys[0]])
|
||||
&& this.problem[problemTypeKeys[0]].length > 1)) {
|
||||
return ProblemTypeKeys.ADVANCED;
|
||||
}
|
||||
@@ -671,7 +673,7 @@ export class OLXParser {
|
||||
}
|
||||
|
||||
getParsedOLXData() {
|
||||
if (_.isEmpty(this.problem)) {
|
||||
if (isEmpty(this.problem)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -720,16 +722,16 @@ export class OLXParser {
|
||||
return {};
|
||||
}
|
||||
const generalFeedback = this.getGeneralFeedback({ answers: answersObject.answers, problemType });
|
||||
if (_.has(answersObject, 'additionalStringAttributes')) {
|
||||
if (has(answersObject, 'additionalStringAttributes')) {
|
||||
additionalAttributes = { ...answersObject.additionalStringAttributes };
|
||||
}
|
||||
|
||||
if (_.has(answersObject, 'groupFeedbackList')) {
|
||||
if (has(answersObject, 'groupFeedbackList')) {
|
||||
groupFeedbackList = answersObject.groupFeedbackList;
|
||||
}
|
||||
const { answers } = answersObject;
|
||||
const settings = { hints };
|
||||
if (ProblemTypeKeys.NUMERIC === problemType && _.has(answers[0], 'tolerance')) {
|
||||
if (ProblemTypeKeys.NUMERIC === problemType && has(answers[0], 'tolerance')) {
|
||||
const toleranceValue = answers[0].tolerance;
|
||||
if (!toleranceValue || toleranceValue.length === 0) {
|
||||
settings.tolerance = { value: null, type: 'None' };
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import { get, has } from 'lodash';
|
||||
import { XMLParser, XMLBuilder } from 'fast-xml-parser';
|
||||
import { ProblemTypeKeys } from '../../../data/constants/problem';
|
||||
import { ToleranceTypes } from '../components/EditProblemView/SettingsWidget/settingsComponents/Tolerance/constants';
|
||||
@@ -169,7 +169,7 @@ class ReactStateOLXParser {
|
||||
choice.push(singleAnswer);
|
||||
}
|
||||
});
|
||||
if (_.has(this.problemState, 'groupFeedbackList') && problemType === ProblemTypeKeys.MULTISELECT) {
|
||||
if (has(this.problemState, 'groupFeedbackList') && problemType === ProblemTypeKeys.MULTISELECT) {
|
||||
compoundhint = this.addGroupFeedbackList();
|
||||
choice.push(...compoundhint);
|
||||
}
|
||||
@@ -333,7 +333,7 @@ class ReactStateOLXParser {
|
||||
answerObject = {
|
||||
':@': {
|
||||
'@_answer': answer.title,
|
||||
'@_type': _.get(this.problemState, 'additionalAttributes.type', 'ci'),
|
||||
'@_type': get(this.problemState, 'additionalAttributes.type', 'ci'),
|
||||
},
|
||||
[problemType]: [...correcthint],
|
||||
};
|
||||
@@ -348,7 +348,7 @@ class ReactStateOLXParser {
|
||||
});
|
||||
answerObject[problemType].push({
|
||||
textline: { '#text': '' },
|
||||
':@': { '@_size': _.get(this.problemState, 'additionalAttributes.textline.size', 20) },
|
||||
':@': { '@_size': get(this.problemState, 'additionalAttributes.textline.size', 20) },
|
||||
});
|
||||
return answerObject;
|
||||
}
|
||||
@@ -490,7 +490,7 @@ class ReactStateOLXParser {
|
||||
* @return {bool}
|
||||
*/
|
||||
hasAttributeWithValue(obj, attr) {
|
||||
return _.has(obj, attr) && _.get(obj, attr, '').toString().trim() !== '';
|
||||
return has(obj, attr) && get(obj, attr, '').toString().trim() !== '';
|
||||
}
|
||||
|
||||
buildOLX() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { XMLParser } from 'fast-xml-parser';
|
||||
import _ from 'lodash';
|
||||
import { includes } from 'lodash';
|
||||
|
||||
import {
|
||||
ShowAnswerTypesKeys,
|
||||
@@ -34,7 +34,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, '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, 'showResetButton', 'show_reset_button', stateSettings, defaultSettings?.showResetButton, true);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
get, isEmpty, isFinite, isNil,
|
||||
} from 'lodash';
|
||||
|
||||
import { ShowAnswerTypes, RandomizationTypesKeys } from '../../../data/constants/problem';
|
||||
|
||||
export const popuplateItem = (parentObject, itemName, statekey, metadata, defaultValue = null, allowNull = false) => {
|
||||
let parent = parentObject;
|
||||
const item = _.get(metadata, itemName, null);
|
||||
const item = get(metadata, itemName, null);
|
||||
|
||||
// if item is null, undefined, or empty string, use defaultValue
|
||||
const finalValue = (_.isNil(item) || item === '') ? defaultValue : item;
|
||||
const finalValue = (isNil(item) || item === '') ? defaultValue : item;
|
||||
|
||||
if (!_.isNil(finalValue) || allowNull) {
|
||||
if (!isNil(finalValue) || allowNull) {
|
||||
parent = { ...parentObject, [statekey]: finalValue };
|
||||
}
|
||||
return parent;
|
||||
@@ -19,18 +21,18 @@ export const parseScoringSettings = (metadata, defaultSettings) => {
|
||||
let scoring = {};
|
||||
|
||||
const attempts = popuplateItem({}, 'max_attempts', 'number', metadata);
|
||||
const initialAttempts = _.get(attempts, 'number', null);
|
||||
const defaultAttempts = _.get(defaultSettings, 'max_attempts', null);
|
||||
const initialAttempts = get(attempts, 'number', null);
|
||||
const defaultAttempts = get(defaultSettings, 'max_attempts', null);
|
||||
attempts.unlimited = false;
|
||||
|
||||
// isFinite checks if value is a finite primitive number.
|
||||
if (!_.isFinite(initialAttempts) || initialAttempts === defaultAttempts) {
|
||||
if (!isFinite(initialAttempts) || initialAttempts === defaultAttempts) {
|
||||
// set number to null in any case as lms will pick default value if it exists.
|
||||
attempts.number = null;
|
||||
}
|
||||
|
||||
// if both block number and default number are null set unlimited to true.
|
||||
if (_.isNil(initialAttempts) && _.isNil(defaultAttempts)) {
|
||||
if (isNil(initialAttempts) && isNil(defaultAttempts)) {
|
||||
attempts.unlimited = true;
|
||||
}
|
||||
|
||||
@@ -48,8 +50,8 @@ export const parseScoringSettings = (metadata, defaultSettings) => {
|
||||
export const parseShowAnswer = (metadata) => {
|
||||
let showAnswer = {};
|
||||
|
||||
const showAnswerType = _.get(metadata, 'showanswer', {});
|
||||
if (!_.isNil(showAnswerType) && showAnswerType in ShowAnswerTypes) {
|
||||
const showAnswerType = get(metadata, 'showanswer', {});
|
||||
if (!isNil(showAnswerType) && showAnswerType in ShowAnswerTypes) {
|
||||
showAnswer = { ...showAnswer, on: showAnswerType };
|
||||
}
|
||||
|
||||
@@ -61,22 +63,22 @@ export const parseShowAnswer = (metadata) => {
|
||||
export const parseSettings = (metadata, defaultSettings) => {
|
||||
let settings = {};
|
||||
|
||||
if (_.isNil(metadata) || _.isEmpty(metadata)) {
|
||||
if (isNil(metadata) || isEmpty(metadata)) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
const scoring = parseScoringSettings(metadata, defaultSettings);
|
||||
if (!_.isEmpty(scoring)) {
|
||||
if (!isEmpty(scoring)) {
|
||||
settings = { ...settings, scoring };
|
||||
}
|
||||
|
||||
const showAnswer = parseShowAnswer(metadata);
|
||||
if (!_.isEmpty(showAnswer)) {
|
||||
if (!isEmpty(showAnswer)) {
|
||||
settings = { ...settings, showAnswer };
|
||||
}
|
||||
|
||||
const randomizationType = _.get(metadata, 'rerandomize', {});
|
||||
if (!_.isEmpty(randomizationType) && Object.values(RandomizationTypesKeys).includes(randomizationType)) {
|
||||
const randomizationType = get(metadata, 'rerandomize', {});
|
||||
if (!isEmpty(randomizationType) && Object.values(RandomizationTypesKeys).includes(randomizationType)) {
|
||||
settings = popuplateItem(settings, 'rerandomize', 'randomization', metadata);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import { has } from 'lodash';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { indexToLetterMap } from '../../../containers/ProblemEditor/data/OLXParser';
|
||||
import { StrictDict } from '../../../utils';
|
||||
@@ -61,17 +61,17 @@ const problem = createSlice({
|
||||
let { correctAnswerCount } = state;
|
||||
const answers = state.answers.map(obj => {
|
||||
if (obj.id === id) {
|
||||
if (_.has(answer, 'correct') && payload.correct) {
|
||||
if (has(answer, 'correct') && payload.correct) {
|
||||
correctAnswerCount += 1;
|
||||
}
|
||||
if (_.has(answer, 'correct') && payload.correct === false && correctAnswerCount > 0) {
|
||||
if (has(answer, 'correct') && payload.correct === false && correctAnswerCount > 0) {
|
||||
correctAnswerCount -= 1;
|
||||
}
|
||||
return { ...obj, ...answer };
|
||||
}
|
||||
// set other answers as incorrect if problem only has one answer correct
|
||||
// and changes object include correct key change
|
||||
if (hasSingleAnswer && _.has(answer, 'correct') && obj.correct) {
|
||||
if (hasSingleAnswer && has(answer, 'correct') && obj.correct) {
|
||||
return { ...obj, correct: false };
|
||||
}
|
||||
return obj;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _ from 'lodash';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import { actions as problemActions } from '../problem';
|
||||
import { actions as requestActions } from '../requests';
|
||||
import { selectors as appSelectors } from '../app';
|
||||
@@ -47,7 +47,7 @@ export const getDataFromOlx = ({ rawOLX, rawSettings, defaultSettings }) => {
|
||||
}
|
||||
const { settings, ...data } = parsedProblem;
|
||||
const parsedSettings = { ...settings, ...parseSettings(rawSettings, defaultSettings) };
|
||||
if (!_.isEmpty(rawOLX) && !_.isEmpty(data)) {
|
||||
if (!isEmpty(rawOLX) && !isEmpty(data)) {
|
||||
return { ...data, rawOLX, settings: parsedSettings };
|
||||
}
|
||||
return { settings: parsedSettings };
|
||||
@@ -79,8 +79,8 @@ export const fetchAdvancedSettings = ({ rawOLX, rawSettings }) => (dispatch) =>
|
||||
};
|
||||
|
||||
export const initializeProblem = (blockValue) => (dispatch, getState) => {
|
||||
const rawOLX = _.get(blockValue, 'data.data', '');
|
||||
const rawSettings = _.get(blockValue, 'data.metadata', {});
|
||||
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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _, { isEmpty } from 'lodash';
|
||||
import { has, find, isEmpty } from 'lodash';
|
||||
import { removeItemOnce } from '../../../utils';
|
||||
import * as requests from './requests';
|
||||
// This 'module' self-import hack enables mocking during tests.
|
||||
@@ -21,8 +21,8 @@ export const loadVideoData = (selectedVideoId, selectedVideoUrl) => (dispatch, g
|
||||
let rawVideoData = blockValueData?.metadata ? blockValueData.metadata : {};
|
||||
const rawVideos = Object.values(selectors.app.videos(state));
|
||||
if (selectedVideoId !== undefined && selectedVideoId !== null) {
|
||||
const selectedVideo = _.find(rawVideos, video => {
|
||||
if (_.has(video, 'edx_video_id')) {
|
||||
const selectedVideo = find(rawVideos, video => {
|
||||
if (has(video, 'edx_video_id')) {
|
||||
return video.edx_video_id === selectedVideoId;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { Form } from '@openedx/paragon';
|
||||
|
||||
const FormGroup = (props) => {
|
||||
@@ -35,13 +35,13 @@ const FormGroup = (props) => {
|
||||
|
||||
{props.children}
|
||||
|
||||
{props.helpText && _.isEmpty(props.errorMessage) && (
|
||||
{props.helpText && isEmpty(props.errorMessage) && (
|
||||
<Form.Control.Feedback type="default" key="help-text">
|
||||
{props.helpText}
|
||||
</Form.Control.Feedback>
|
||||
)}
|
||||
|
||||
{!_.isEmpty(props.errorMessage) && (
|
||||
{!isEmpty(props.errorMessage) && (
|
||||
<Form.Control.Feedback
|
||||
type="invalid"
|
||||
key="error"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
@@ -57,7 +57,7 @@ const TableActions = ({
|
||||
) : null}
|
||||
<Dropdown.Item
|
||||
onClick={() => handleBulkDownload(selectedFlatRows)}
|
||||
disabled={_.isEmpty(selectedFlatRows)}
|
||||
disabled={isEmpty(selectedFlatRows)}
|
||||
>
|
||||
<FormattedMessage {...messages.downloadTitle} />
|
||||
</Dropdown.Item>
|
||||
@@ -65,7 +65,7 @@ const TableActions = ({
|
||||
<Dropdown.Item
|
||||
data-testid="open-delete-confirmation-button"
|
||||
onClick={() => handleOpenDeleteConfirmation(selectedFlatRows)}
|
||||
disabled={_.isEmpty(selectedFlatRows)}
|
||||
disabled={isEmpty(selectedFlatRows)}
|
||||
>
|
||||
<FormattedMessage {...messages.deleteTitle} />
|
||||
</Dropdown.Item>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import last from 'lodash/last';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { FileUpload as FileUploadIcon } from '@openedx/paragon/icons';
|
||||
@@ -35,9 +35,9 @@ const CourseUploadImage = ({
|
||||
const assetsUrl = () => new URL(`/assets/${courseId}`, getConfig().STUDIO_BASE_URL);
|
||||
|
||||
const handleChangeImageAsset = (path) => {
|
||||
const assetPath = _.last(path.split('/'));
|
||||
const assetPath = last(path.split('/'));
|
||||
// If image path is entered directly, we need to strip the asset prefix
|
||||
const imageName = _.last(assetPath.split('block@'));
|
||||
const imageName = last(assetPath.split('block@'));
|
||||
onChange(path, assetImageField);
|
||||
if (imageNameField) {
|
||||
onChange(imageName, imageNameField);
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from '@openedx/paragon';
|
||||
import { AppContext } from '@edx/frontend-platform/react';
|
||||
import { FieldArray, useFormikContext } from 'formik';
|
||||
import _ from 'lodash';
|
||||
import size from 'lodash/size';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import FormSwitchGroup from '../../../../../generic/FormSwitchGroup';
|
||||
import messages from '../../messages';
|
||||
@@ -34,7 +34,7 @@ const DivisionByGroupFields = ({ intl }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (divideByCohorts) {
|
||||
if (!divideCourseTopicsByCohorts && _.size(discussionTopics) !== _.size(divideDiscussionIds)) {
|
||||
if (!divideCourseTopicsByCohorts && size(discussionTopics) !== size(divideDiscussionIds)) {
|
||||
setFieldValue('divideDiscussionIds', discussionTopics.map(topic => topic.id));
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Form } from '@openedx/paragon';
|
||||
import { useFormikContext } from 'formik';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import { startCase, toLower } from 'lodash';
|
||||
|
||||
import messages from '../../../messages';
|
||||
import RestrictDatesInput from './RestrictDatesInput';
|
||||
@@ -53,7 +53,7 @@ const DiscussionRestrictionItem = ({
|
||||
collapseHeadingText={formatRestrictedDates(restrictedDate)}
|
||||
badgeVariant={badgeVariant[restrictedDate.status]}
|
||||
badgeStatus={intl.formatMessage(messages.restrictedDatesStatus, {
|
||||
status: _.startCase(_.toLower(restrictedDate.status)),
|
||||
status: startCase(toLower(restrictedDate.status)),
|
||||
})}
|
||||
/>
|
||||
), [restrictedDate]);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button } from '@openedx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { FieldArray, useFormikContext } from 'formik';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import _ from 'lodash';
|
||||
import { remove as removeElements, uniq, uniqBy } from 'lodash';
|
||||
import messages from '../../../messages';
|
||||
import TopicItem from './TopicItem';
|
||||
import { OpenedXConfigFormContext } from '../../openedx/OpenedXConfigFormProvider';
|
||||
@@ -36,10 +36,10 @@ const DiscussionTopics = ({ intl }) => {
|
||||
} else {
|
||||
setValidDiscussionTopics(currentValidTopics => {
|
||||
const allDiscussionTopics = [...currentValidTopics, ...discussionTopics.filter(topic => topic.id === id)];
|
||||
const allValidTopics = _.remove(allDiscussionTopics, topic => topic.name !== '');
|
||||
return _.uniqBy(allValidTopics, 'id');
|
||||
const allValidTopics = removeElements(allDiscussionTopics, topic => topic.name !== '');
|
||||
return uniqBy(allValidTopics, 'id');
|
||||
});
|
||||
setFieldValue('divideDiscussionIds', _.uniq([...divideDiscussionIds, id]));
|
||||
setFieldValue('divideDiscussionIds', uniq([...divideDiscussionIds, id]));
|
||||
}
|
||||
}, [divideDiscussionIds, discussionTopics]);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { getIn } from 'formik';
|
||||
import { restrictedDatesStatus as constants } from '../data/constants';
|
||||
|
||||
@@ -48,7 +48,7 @@ export const decodeDateTime = (date, time) => {
|
||||
};
|
||||
|
||||
export const sortRestrictedDatesByStatus = (data, status, order) => (
|
||||
_.orderBy(
|
||||
orderBy(
|
||||
data.filter(date => date.status === status),
|
||||
[(obj) => decodeDateTime(obj.startDate, startOfDayTime(obj.startTime))],
|
||||
[order],
|
||||
|
||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { Remove, Check } from '@openedx/paragon/icons';
|
||||
import { DataTable } from '@openedx/paragon';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import _ from 'lodash';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
|
||||
import messages from './messages';
|
||||
import appMessages from '../app-config-form/messages';
|
||||
@@ -13,7 +13,7 @@ import './FeaturesTable.scss';
|
||||
const FeaturesTable = ({ apps, features, intl }) => {
|
||||
const {
|
||||
basic, partial, full, common,
|
||||
} = _.groupBy(features, (feature) => feature.featureSupportType);
|
||||
} = groupBy(features, (feature) => feature.featureSupportType);
|
||||
|
||||
const createRow = (feature) => {
|
||||
const appCheckmarkCells = {};
|
||||
|
||||
Reference in New Issue
Block a user