Skip to content

Commit 5991fd3

Browse files
refactor: Load waffle flags using React Query (#2068)
* refactor: use React Query to load waffle flags * test: add test case * fix: more clear handling of data loading and fallbacks * refactor: simplify handling of useReactMarkdownEditor * test: use new mockWaffleFlags() helper * test: simplify test mocks in hooks.test.js * refactor: avoid duplicating flag names, clarify how defaults work
1 parent 9a2dac6 commit 5991fd3

44 files changed

Lines changed: 355 additions & 268 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/CourseAuthoringPage.jsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from 'react-router-dom';
88
import { StudioFooterSlot } from '@edx/frontend-component-footer';
99
import Header from './header';
10-
import { fetchCourseDetail, fetchWaffleFlags } from './data/thunks';
10+
import { fetchCourseDetail } from './data/thunks';
1111
import { useModel } from './generic/model-store';
1212
import NotFoundAlert from './generic/NotFoundAlert';
1313
import PermissionDeniedAlert from './generic/PermissionDeniedAlert';
@@ -21,7 +21,6 @@ const CourseAuthoringPage = ({ courseId, children }) => {
2121

2222
useEffect(() => {
2323
dispatch(fetchCourseDetail(courseId));
24-
dispatch(fetchWaffleFlags(courseId));
2524
}, [courseId]);
2625

2726
useEffect(() => {

src/advanced-settings/AdvancedSettings.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
Container, Button, Layout, StatefulButton, TransitionReplace,
66
} from '@openedx/paragon';
77
import { CheckCircle, Info, Warning } from '@openedx/paragon/icons';
8-
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
8+
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
99
import Placeholder from '../editors/Placeholder';
1010

1111
import AlertProctoringError from '../generic/AlertProctoringError';
@@ -26,7 +26,8 @@ import messages from './messages';
2626
import ModalError from './modal-error/ModalError';
2727
import getPageHeadTitle from '../generic/utils';
2828

29-
const AdvancedSettings = ({ intl, courseId }) => {
29+
const AdvancedSettings = ({ courseId }) => {
30+
const intl = useIntl();
3031
const dispatch = useDispatch();
3132
const [saveSettingsPrompt, showSaveSettingsPrompt] = useState(false);
3233
const [showDeprecated, setShowDeprecated] = useState(false);
@@ -278,8 +279,7 @@ const AdvancedSettings = ({ intl, courseId }) => {
278279
};
279280

280281
AdvancedSettings.propTypes = {
281-
intl: intlShape.isRequired,
282282
courseId: PropTypes.string.isRequired,
283283
};
284284

285-
export default injectIntl(AdvancedSettings);
285+
export default AdvancedSettings;
Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
1+
// @ts-check
12
import React from 'react';
2-
import {
3-
FormattedMessage,
4-
injectIntl,
5-
intlShape,
6-
} from '@edx/frontend-platform/i18n';
3+
import { FormattedMessage } from '@edx/frontend-platform/i18n';
74
import PropTypes from 'prop-types';
85

96
import { HelpSidebar } from '../../generic/help-sidebar';
107
import messages from './messages';
118

12-
const SettingsSidebar = ({ intl, courseId, proctoredExamSettingsUrl }) => (
9+
const SettingsSidebar = ({ courseId, proctoredExamSettingsUrl = '' }) => (
1310
<HelpSidebar
1411
courseId={courseId}
1512
proctoredExamSettingsUrl={proctoredExamSettingsUrl}
1613
showOtherSettings
1714
>
1815
<h4 className="help-sidebar-about-title">
19-
{intl.formatMessage(messages.about)}
16+
<FormattedMessage {...messages.about} />
2017
</h4>
2118
<p className="help-sidebar-about-descriptions">
22-
{intl.formatMessage(messages.aboutDescription1)}
19+
<FormattedMessage {...messages.aboutDescription1} />
2320
</p>
2421
<p className="help-sidebar-about-descriptions">
25-
{intl.formatMessage(messages.aboutDescription2)}
22+
<FormattedMessage {...messages.aboutDescription2} />
2623
</p>
2724
<p className="help-sidebar-about-descriptions">
2825
<FormattedMessage
@@ -34,14 +31,9 @@ const SettingsSidebar = ({ intl, courseId, proctoredExamSettingsUrl }) => (
3431
</HelpSidebar>
3532
);
3633

37-
SettingsSidebar.defaultProps = {
38-
proctoredExamSettingsUrl: '',
39-
};
40-
4134
SettingsSidebar.propTypes = {
42-
intl: intlShape.isRequired,
4335
courseId: PropTypes.string.isRequired,
4436
proctoredExamSettingsUrl: PropTypes.string,
4537
};
4638

47-
export default injectIntl(SettingsSidebar);
39+
export default SettingsSidebar;

src/advanced-settings/settings-sidebar/SettingsSidebar.test.jsx

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,21 @@
1-
import React from 'react';
2-
import { render } from '@testing-library/react';
3-
import { IntlProvider } from '@edx/frontend-platform/i18n';
4-
import { initializeMockApp } from '@edx/frontend-platform';
5-
import { AppProvider } from '@edx/frontend-platform/react';
6-
7-
import initializeStore from '../../store';
1+
// @ts-check
2+
import { initializeMocks, render } from '../../testUtils';
83
import SettingsSidebar from './SettingsSidebar';
94
import messages from './messages';
105

116
const courseId = 'course-123';
12-
let store;
13-
14-
const RootWrapper = () => (
15-
<AppProvider store={store}>
16-
<IntlProvider locale="en" messages={{}}>
17-
<SettingsSidebar intl={{ formatMessage: jest.fn() }} courseId={courseId} />
18-
</IntlProvider>
19-
</AppProvider>
20-
);
217

228
describe('<SettingsSidebar />', () => {
239
beforeEach(() => {
24-
initializeMockApp({
25-
authenticatedUser: {
26-
userId: 3,
27-
username: 'abc123',
28-
administrator: true,
29-
roles: [],
30-
},
31-
});
32-
store = initializeStore();
10+
initializeMocks();
3311
});
3412
it('renders about and other sidebar titles correctly', () => {
35-
const { getByText } = render(<RootWrapper />);
13+
const { getByText } = render(<SettingsSidebar courseId={courseId} />);
3614
expect(getByText(messages.about.defaultMessage)).toBeInTheDocument();
3715
expect(getByText(messages.other.defaultMessage)).toBeInTheDocument();
3816
});
3917
it('renders about descriptions correctly', () => {
40-
const { getByText } = render(<RootWrapper />);
18+
const { getByText } = render(<SettingsSidebar courseId={courseId} />);
4119
const aboutThirtyDescription = getByText('When you enter strings as policy values, ensure that you use double quotation marks (“) around the string. Do not use single quotation marks (‘).');
4220
expect(getByText(messages.aboutDescription1.defaultMessage)).toBeInTheDocument();
4321
expect(getByText(messages.aboutDescription2.defaultMessage)).toBeInTheDocument();

src/course-checklist/ChecklistSection/ChecklistItemBody.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import PropTypes from 'prop-types';
22
import { Link } from 'react-router-dom';
33
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
44
import { ActionRow, Button, Icon } from '@openedx/paragon';
5-
import { useSelector } from 'react-redux';
65
import { CheckCircle, RadioButtonUnchecked } from '@openedx/paragon/icons';
76
import { getConfig } from '@edx/frontend-platform';
87

9-
import { getWaffleFlags } from '../../data/selectors';
8+
import { useWaffleFlags } from '../../data/apiHooks';
109
import messages from './messages';
1110

1211
const getUpdateLinks = (courseId, waffleFlags) => {
@@ -35,7 +34,7 @@ const ChecklistItemBody = ({
3534
isCompleted,
3635
}) => {
3736
const intl = useIntl();
38-
const waffleFlags = useSelector(getWaffleFlags);
37+
const waffleFlags = useWaffleFlags(courseId);
3938
const updateLinks = getUpdateLinks(courseId, waffleFlags);
4039

4140
return (

src/course-checklist/ChecklistSection/ChecklistItemComment.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import PropTypes from 'prop-types';
2-
import { useSelector } from 'react-redux';
32
import { injectIntl, FormattedMessage, FormattedNumber } from '@edx/frontend-platform/i18n';
43
import { Icon } from '@openedx/paragon';
54
import { Link } from 'react-router-dom';
65
import { ModeComment } from '@openedx/paragon/icons';
76
import { getConfig } from '@edx/frontend-platform';
8-
import { getWaffleFlags } from '../../data/selectors';
7+
import { useWaffleFlags } from '../../data/apiHooks';
98
import messages from './messages';
109

1110
const ChecklistItemComment = ({
1211
courseId,
1312
checkId,
1413
data,
1514
}) => {
16-
const waffleFlags = useSelector(getWaffleFlags);
15+
const waffleFlags = useWaffleFlags(courseId);
1716

1817
const getPathToCourseOutlinePage = (assignmentId) => (waffleFlags.useNewCourseOutlinePage
1918
? `/course/${courseId}#${assignmentId}` : `${getConfig().STUDIO_BASE_URL}/course/${courseId}#${assignmentId}`);

src/course-outline/hooks.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { getConfig } from '@edx/frontend-platform';
66

77
import moment from 'moment';
88
import { getSavingStatus as getGenericSavingStatus } from '../generic/data/selectors';
9-
import { getWaffleFlags } from '../data/selectors';
9+
import { useWaffleFlags } from '../data/apiHooks';
1010
import { RequestStatus } from '../data/constants';
1111
import { COURSE_BLOCK_NAMES } from './constants';
1212
import {
@@ -61,7 +61,7 @@ import {
6161
const useCourseOutline = ({ courseId }) => {
6262
const dispatch = useDispatch();
6363
const navigate = useNavigate();
64-
const waffleFlags = useSelector(getWaffleFlags);
64+
const waffleFlags = useWaffleFlags(courseId);
6565

6666
const {
6767
reindexLink,

src/course-outline/status-bar/StatusBar.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import {
88
} from '@openedx/paragon';
99
import { Link } from 'react-router-dom';
1010
import { AppContext } from '@edx/frontend-platform/react';
11-
import { useSelector } from 'react-redux';
1211

1312
import { ContentTagsDrawerSheet } from '../../content-tags-drawer';
1413
import TagCount from '../../generic/tag-count';
1514
import { useHelpUrls } from '../../help-urls/hooks';
16-
import { getWaffleFlags } from '../../data/selectors';
15+
import { useWaffleFlags } from '../../data/apiHooks';
1716
import { VIDEO_SHARING_OPTIONS } from '../constants';
1817
import { useContentTagsCount } from '../../generic/data/apiHooks';
1918
import messages from './messages';
@@ -46,7 +45,7 @@ const StatusBar = ({
4645
}) => {
4746
const intl = useIntl();
4847
const { config } = useContext(AppContext);
49-
const waffleFlags = useSelector(getWaffleFlags);
48+
const waffleFlags = useWaffleFlags(courseId);
5049

5150
const {
5251
courseReleaseDate,

src/course-unit/CourseUnit.test.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ import xblockContainerIframeMessages from './xblock-container-iframe/messages';
6262
import headerNavigationsMessages from './header-navigations/messages';
6363
import sidebarMessages from './sidebar/messages';
6464
import messages from './messages';
65-
import * as selectors from '../data/selectors';
65+
import { mockWaffleFlags } from '../data/apiHooks.mock';
6666

6767
let axiosMock;
6868
let store;
@@ -847,7 +847,7 @@ describe('<CourseUnit />', () => {
847847
});
848848

849849
it('handles creating Video xblock and showing editor modal using videogalleryflow', async () => {
850-
const waffleSpy = jest.spyOn(selectors, 'getWaffleFlags').mockReturnValue({ useVideoGalleryFlow: true });
850+
const waffleSpy = mockWaffleFlags({ useVideoGalleryFlow: true });
851851

852852
axiosMock
853853
.onPost(postXBlockBaseApiUrl({ type: 'video', category: 'video', parentLocator: blockId }))

src/course-unit/add-component/AddComponent.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from '@openedx/paragon';
99

1010
import { getCourseSectionVertical } from '../data/selectors';
11-
import { getWaffleFlags } from '../../data/selectors';
11+
import { useWaffleFlags } from '../../data/apiHooks';
1212
import { COMPONENT_TYPES } from '../../generic/block-type-utils/constants';
1313
import ComponentModalView from './add-component-modals/ComponentModalView';
1414
import AddComponentButton from './add-component-btn';
@@ -45,7 +45,7 @@ const AddComponent = ({
4545
const [selectedComponents, setSelectedComponents] = useState([]);
4646
const [usageId, setUsageId] = useState(null);
4747
const { sendMessageToIframe } = useIframe();
48-
const { useVideoGalleryFlow, useReactMarkdownEditor } = useSelector(getWaffleFlags);
48+
const { useVideoGalleryFlow } = useWaffleFlags(courseId ?? undefined);
4949

5050
const receiveMessage = useCallback(({ data: { type, payload } }) => {
5151
if (type === messageTypes.showMultipleComponentPicker) {
@@ -266,7 +266,6 @@ const AddComponent = ({
266266
courseId={courseId}
267267
blockType={blockType}
268268
blockId={newBlockId}
269-
isMarkdownEditorEnabledForCourse={useReactMarkdownEditor}
270269
studioEndpointUrl={getConfig().STUDIO_BASE_URL}
271270
lmsEndpointUrl={getConfig().LMS_BASE_URL}
272271
onClose={closeXBlockEditorModal}

0 commit comments

Comments
 (0)