Skip to content

Commit

Permalink
feat: moving new commits from upstream repository (#35)
Browse files Browse the repository at this point in the history
* chore: add paragon messages (openedx#530)

* chore: add paragon messages (openedx#530) (openedx#534)

Co-authored-by: Mashal Malik <107556986+Mashal-m@users.noreply.github.com>

* feat: make placeholder depend on api response

* feat: include paragon in atlas pull (openedx#538)

This pull request is part of the [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) which is sparked by the [Translation Infrastructure update OEP-58](https://open-edx-proposals.readthedocs.io/en/latest/architectural-decisions/oep-0058-arch-translations-management.html#specification).

* fix: ui bugs (openedx#542)

---------

Co-authored-by: Mashal Malik <107556986+Mashal-m@users.noreply.github.com>
Co-authored-by: sundasnoreen12 <72802712+sundasnoreen12@users.noreply.github.com>
Co-authored-by: Omar Al-Ithawi <i@omardo.com>
Co-authored-by: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com>
  • Loading branch information
5 people authored Aug 1, 2023
1 parent 9c1cc64 commit 1de326c
Show file tree
Hide file tree
Showing 30 changed files with 563 additions and 257 deletions.
3 changes: 1 addition & 2 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,11 @@ ENABLE_NEW_FILES_UPLOADS_PAGE = false
ENABLE_NEW_VIDEO_UPLOAD_PAGE = false
ENABLE_NEW_SCHEDULE_DETAILS_PAGE = false
ENABLE_NEW_GRADING_PAGE = false
ENABLE_NEW_COURSE_TEAM_PAGE = true
ENABLE_NEW_COURSE_TEAM_PAGE = false
ENABLE_NEW_ADVANCED_SETTINGS_PAGE = false
ENABLE_NEW_IMPORT_PAGE = false
ENABLE_NEW_EXPORT_PAGE = false
ENABLE_UNIT_PAGE = false
ENABLE_NEW_CUSTOM_PAGES = false
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = false
ENABLE_CREDIT_ELIGIBILITY = false
BBB_LEARN_MORE_URL=''
Expand Down
1 change: 0 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ ENABLE_NEW_ADVANCED_SETTINGS_PAGE = true
ENABLE_NEW_IMPORT_PAGE = false
ENABLE_NEW_EXPORT_PAGE = false
ENABLE_UNIT_PAGE = false
ENABLE_NEW_CUSTOM_PAGES = true
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = false
ENABLE_CREDIT_ELIGIBILITY = true
BBB_LEARN_MORE_URL=''
Expand Down
2 changes: 0 additions & 2 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,9 @@ ENABLE_NEW_VIDEO_UPLOAD_PAGE = true
ENABLE_NEW_SCHEDULE_DETAILS_PAGE = true
ENABLE_NEW_GRADING_PAGE = true
ENABLE_NEW_COURSE_TEAM_PAGE = true
ENABLE_NEW_ADVANCED_SETTINGS_PAGE = true
ENABLE_NEW_IMPORT_PAGE = true
ENABLE_NEW_EXPORT_PAGE = true
ENABLE_UNIT_PAGE = true
ENABLE_NEW_CUSTOM_PAGES = true
ENABLE_VIDEO_UPLOAD_PAGE_LINK_IN_CONTENT_DROPDOWN = true
ENABLE_CREDIT_ELIGIBILITY = true
BBB_LEARN_MORE_URL=''
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ pull_translations:
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull --filter=$(transifex_langs) \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-app-course-authoring/src/i18n/messages:frontend-app-course-authoring

$(intl_imports) frontend-component-footer frontend-app-course-authoring
$(intl_imports) paragon frontend-component-footer frontend-app-course-authoring
endif

# This target is used by Travis.
Expand Down
10 changes: 2 additions & 8 deletions src/CourseAuthoringRoutes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ const CourseAuthoringRoutes = ({ courseId }) => {
<ProctoredExamSettings courseId={courseId} />
</PageRoute>
<PageRoute path={`${path}/custom-pages`}>
{process.env.ENABLE_NEW_CUSTOM_PAGES === 'true'
&& (
<CustomPages courseId={courseId} />
)}
<CustomPages courseId={courseId} />
</PageRoute>
<PageRoute path={`${path}/container/:blockId`}>
{process.env.ENABLE_UNIT_PAGE === 'true'
Expand Down Expand Up @@ -113,10 +110,7 @@ const CourseAuthoringRoutes = ({ courseId }) => {
)}
</PageRoute>
<PageRoute path={`${path}/settings/advanced`}>
{process.env.ENABLE_NEW_ADVANCED_SETTINGS_PAGE === 'true'
&& (
<AdvancedSettings courseId={courseId} />
)}
<AdvancedSettings courseId={courseId} />
</PageRoute>
<PageRoute path={`${path}/import`}>
{process.env.ENABLE_NEW_IMPORT_PAGE === 'true'
Expand Down
128 changes: 62 additions & 66 deletions src/advanced-settings/AdvancedSettings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import {
Container, Button, Layout, StatefulButton,
Container, Button, Layout, StatefulButton, TransitionReplace,
} from '@edx/paragon';
import { CheckCircle, Info, Warning } from '@edx/paragon/icons';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import Placeholder from '@edx/frontend-lib-content-components';

import AlertProctoringError from '../generic/AlertProctoringError';
import InternetConnectionAlert from '../generic/internet-connection-alert';
Expand All @@ -24,21 +25,28 @@ import messages from './messages';
import ModalError from './modal-error/ModalError';

const AdvancedSettings = ({ intl, courseId }) => {
const advancedSettingsData = useSelector(getCourseAppSettings);
const savingStatus = useSelector(getSavingStatus);
const proctoringExamErrors = useSelector(getProctoringExamErrors);
const settingsWithSendErrors = useSelector(getSendRequestErrors) || {};
const dispatch = useDispatch();
const [saveSettingsPrompt, showSaveSettingsPrompt] = useState(false);
const [showDeprecated, setShowDeprecated] = useState(false);
const [errorModal, showErrorModal] = useState(false);
const [editedSettings, setEditedSettings] = useState({});
const [errorFields, setErrorFields] = useState([]);
const [showSuccessAlert, setShowSuccessAlert] = useState(false);
const loadingSettingsStatus = useSelector(getLoadingStatus);
const [isQueryPending, setIsQueryPending] = useState(false);
const [isEditableState, setIsEditableState] = useState(false);
const [hasInternetConnectionError, setInternetConnectionError] = useState(false);

useEffect(() => {
dispatch(fetchCourseAppSettings(courseId));
dispatch(fetchProctoringExamErrors(courseId));
}, [courseId]);

const advancedSettingsData = useSelector(getCourseAppSettings);
const savingStatus = useSelector(getSavingStatus);
const proctoringExamErrors = useSelector(getProctoringExamErrors);
const settingsWithSendErrors = useSelector(getSendRequestErrors) || {};
const loadingSettingsStatus = useSelector(getLoadingStatus);

const isLoading = loadingSettingsStatus === RequestStatus.IN_PROGRESS;
const updateSettingsButtonState = {
labels: {
Expand All @@ -48,20 +56,14 @@ const AdvancedSettings = ({ intl, courseId }) => {
disabledStates: ['pending'],
};

useEffect(() => {
dispatch(fetchCourseAppSettings(courseId));
dispatch(fetchProctoringExamErrors(courseId));
}, [courseId]);

useEffect(() => {
if (savingStatus === RequestStatus.SUCCESSFUL) {
setIsQueryPending(false);
setShowSuccessAlert(true);
setIsEditableState(false);
setTimeout(() => setShowSuccessAlert(false), 15000);
window.scrollTo({ top: 0, behavior: 'smooth' });

if (!isEditableState) {
showSaveSettingsPrompt(false);
}
showSaveSettingsPrompt(false);
} else if (savingStatus === RequestStatus.FAILED && !hasInternetConnectionError) {
setErrorFields(settingsWithSendErrors);
showErrorModal(true);
Expand All @@ -72,27 +74,19 @@ const AdvancedSettings = ({ intl, courseId }) => {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;
}

const handleSettingChange = (e, settingName) => {
const { value } = e.target;
if (!saveSettingsPrompt) {
showSaveSettingsPrompt(true);
}
setIsEditableState(true);
setShowSuccessAlert(false);
setEditedSettings((prevEditedSettings) => ({
...prevEditedSettings,
[settingName]: value,
}));
};
if (loadingSettingsStatus === RequestStatus.DENIED) {
return (
<div className="row justify-contnt-center m-6">
<Placeholder />
</div>
);
}

const handleResetSettingsValues = () => {
setIsEditableState(false);
showErrorModal(false);
setEditedSettings({});
showSaveSettingsPrompt(false);
setInternetConnectionError(false);
setIsQueryPending(false);
};

const handleSettingBlur = () => {
Expand All @@ -103,9 +97,7 @@ const AdvancedSettings = ({ intl, courseId }) => {
const isValid = validateAdvancedSettingsData(editedSettings, setErrorFields, setEditedSettings);
if (isValid) {
setIsQueryPending(true);
setIsEditableState(false);
} else {
setIsQueryPending(false);
showSaveSettingsPrompt(false);
showErrorModal(!errorModal);
}
Expand All @@ -115,7 +107,6 @@ const AdvancedSettings = ({ intl, courseId }) => {
setInternetConnectionError(true);
showSaveSettingsPrompt(false);
setShowSuccessAlert(false);
setIsQueryPending(false);
};

const handleQueryProcessing = () => {
Expand All @@ -124,15 +115,13 @@ const AdvancedSettings = ({ intl, courseId }) => {
};

const handleManuallyChangeClick = (setToState) => {
setIsEditableState(true);
showErrorModal(setToState);
showSaveSettingsPrompt(true);
setIsQueryPending(false);
};

return (
<>
<Container size="xl" className="m-4">
<Container size="xl" className="px-4">
<div className="setting-header mt-5">
{(proctoringExamErrors?.length > 0) && (
<AlertProctoringError
Expand All @@ -143,17 +132,27 @@ const AdvancedSettings = ({ intl, courseId }) => {
aria-describedby={intl.formatMessage(messages.alertProctoringDescribedby)}
/>
)}
<AlertMessage
show={showSuccessAlert}
variant="success"
icon={CheckCircle}
title={intl.formatMessage(messages.alertSuccess)}
description={intl.formatMessage(messages.alertSuccessDescriptions)}
aria-hidden="true"
aria-labelledby={intl.formatMessage(messages.alertSuccessAriaLabelledby)}
aria-describedby={intl.formatMessage(messages.alertSuccessAriaDescribedby)}
/>
<TransitionReplace>
{showSuccessAlert ? (
<AlertMessage
key={intl.formatMessage(messages.alertSuccessAriaLabelledby)}
show={showSuccessAlert}
variant="success"
icon={CheckCircle}
title={intl.formatMessage(messages.alertSuccess)}
description={intl.formatMessage(messages.alertSuccessDescriptions)}
aria-hidden="true"
aria-labelledby={intl.formatMessage(messages.alertSuccessAriaLabelledby)}
aria-describedby={intl.formatMessage(messages.alertSuccessAriaDescribedby)}
/>
) : null}
</TransitionReplace>
</div>
<SubHeader
subtitle={intl.formatMessage(messages.headingSubtitle)}
title={intl.formatMessage(messages.headingTitle)}
contentTitle={intl.formatMessage(messages.policy)}
/>
<section className="setting-items mb-4">
<Layout
lg={[{ span: 9 }, { span: 3 }]}
Expand All @@ -166,18 +165,13 @@ const AdvancedSettings = ({ intl, courseId }) => {
<article>
<div>
<section className="setting-items-policies">
<SubHeader
subtitle={intl.formatMessage(messages.headingSubtitle)}
title={intl.formatMessage(messages.headingTitle)}
contentTitle={intl.formatMessage(messages.policy)}
instruction={(
<FormattedMessage
id="course-authoring.advanced-settings.policies.description"
defaultMessage="{notice} Do not modify these policies unless you are familiar with their purpose."
values={{ notice: <strong>Warning: </strong> }}
/>
)}
/>
<div className="small">
<FormattedMessage
id="course-authoring.advanced-settings.policies.description"
defaultMessage="{notice} Do not modify these policies unless you are familiar with their purpose."
values={{ notice: <strong>Warning: </strong> }}
/>
</div>
<div className="setting-items-deprecated-setting">
<Button
variant={showDeprecated ? 'outline-brand' : 'tertiary'}
Expand All @@ -196,20 +190,22 @@ const AdvancedSettings = ({ intl, courseId }) => {
</Button>
</div>
<ul className="setting-items-list p-0">
{Object.keys(advancedSettingsData).sort().map((settingName) => {
{Object.keys(advancedSettingsData).map((settingName) => {
const settingData = advancedSettingsData[settingName];
const editedValue = editedSettings[settingName] !== undefined
? editedSettings[settingName] : JSON.stringify(settingData.value, null, 4);

if (settingData.deprecated && !showDeprecated) {
return null;
}
return (
<SettingCard
key={settingName}
settingData={settingData}
onChange={(e) => handleSettingChange(e, settingName)}
showDeprecated={showDeprecated}
name={settingName}
value={editedValue}
showSaveSettingsPrompt={showSaveSettingsPrompt}
saveSettingsPrompt={saveSettingsPrompt}
setEdited={setEditedSettings}
handleBlur={handleSettingBlur}
isEditableState={isEditableState}
setIsEditableState={setIsEditableState}
/>
);
})}
Expand All @@ -225,7 +221,7 @@ const AdvancedSettings = ({ intl, courseId }) => {
</section>
</Container>
<div className="alert-toast">
{!isEditableState && (
{isQueryPending && (
<InternetConnectionAlert
isFailed={savingStatus === RequestStatus.FAILED}
isQueryPending={isQueryPending}
Expand Down
49 changes: 39 additions & 10 deletions src/advanced-settings/AdvancedSettings.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { render, fireEvent, waitFor } from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';

import initializeStore from '../store';
import { executeThunk } from '../utils';
import { advancedSettingsMock } from './__mocks__';
import { getCourseAdvancedSettingsApiUrl } from './data/api';
import { updateCourseAppSetting } from './data/thunks';
import AdvancedSettings from './AdvancedSettings';
import messages from './messages';

Expand Down Expand Up @@ -70,10 +72,11 @@ describe('<AdvancedSettings />', () => {
});
});
it('should render setting element', async () => {
const { getByText } = render(<RootWrapper />);
const { getByText, queryByText } = render(<RootWrapper />);
await waitFor(() => {
const advancedModuleListTitle = getByText(/Advanced Module List/i);
expect(advancedModuleListTitle).toBeInTheDocument();
expect(queryByText('Certificate web/html view enabled')).toBeNull();
});
});
it('should change to onСhange', async () => {
Expand Down Expand Up @@ -112,24 +115,50 @@ describe('<AdvancedSettings />', () => {
fireEvent.click(showDeprecatedItemsBtn);
expect(getByText(/Hide Deprecated Settings/i)).toBeInTheDocument();
});
expect(getByText('Certificate web/html view enabled')).toBeInTheDocument();
});
it('should reset to default value on click on Cancel button', async () => {
const { getByLabelText, getByText } = render(<RootWrapper />);
let textarea;
await waitFor(() => {
const textarea = getByLabelText(/Advanced Module List/i);
fireEvent.change(textarea, { target: { value: '[3, 2, 1]' } });
fireEvent.click(getByText(messages.buttonCancelText.defaultMessage));
expect(textarea.value).toBe('[]');
textarea = getByLabelText(/Advanced Module List/i);
});
fireEvent.change(textarea, { target: { value: '[3, 2, 1]' } });
expect(textarea.value).toBe('[3, 2, 1]');
fireEvent.click(getByText(messages.buttonCancelText.defaultMessage));
expect(textarea.value).toBe('[]');
});
it('should update the textarea value and display the updated value after clicking "Change manually"', async () => {
const { getByLabelText, getByText } = render(<RootWrapper />);
let textarea;
await waitFor(() => {
const textarea = getByLabelText(/Advanced Module List/i);
fireEvent.change(textarea, { target: { value: '[3, 2, 1' } });
fireEvent.click(getByText(messages.buttonSaveText.defaultMessage));
fireEvent.click(getByText(/Change manually/i));
expect(textarea.value).toBe('[3, 2, 1');
textarea = getByLabelText(/Advanced Module List/i);
});
fireEvent.change(textarea, { target: { value: '[3, 2, 1,' } });
expect(textarea.value).toBe('[3, 2, 1,');
fireEvent.click(getByText('Save changes'));
fireEvent.click(getByText('Change manually'));
expect(textarea.value).toBe('[3, 2, 1,');
});
it('should show success alert after save', async () => {
const { getByLabelText, getByText } = render(<RootWrapper />);
let textarea;
await waitFor(() => {
textarea = getByLabelText(/Advanced Module List/i);
});
fireEvent.change(textarea, { target: { value: '[3, 2, 1]' } });
expect(textarea.value).toBe('[3, 2, 1]');
axiosMock
.onPatch(`${getCourseAdvancedSettingsApiUrl(courseId)}`)
.reply(200, {
...advancedSettingsMock,
advancedModules: {
...advancedSettingsMock.advancedModules,
value: [3, 2, 1],
},
});
fireEvent.click(getByText('Save changes'));
await executeThunk(updateCourseAppSetting(courseId, [3, 2, 1]), store.dispatch);
expect(getByText('Your policy changes have been saved.')).toBeInTheDocument();
});
});
Loading

0 comments on commit 1de326c

Please sign in to comment.