From 14873839451a7a2d1bf4c1535c0ae48405dcb299 Mon Sep 17 00:00:00 2001 From: Mashal Malik <107556986+Mashal-m@users.noreply.github.com> Date: Fri, 21 Jul 2023 11:08:39 +0500 Subject: [PATCH] chore: add paragon messages (#530) --- src/i18n/index.js | 3 +- .../discussions/DiscussionsSettings.test.jsx | 87 ++++++++++------- .../discussions/app-list/AppList.jsx | 95 ++++++++++++++++-- .../discussions/app-list/AppList.scss | 31 +++++- .../discussions/app-list/AppList.test.jsx | 25 +++-- .../discussions/app-list/messages.js | 20 ++++ .../discussions/data/api.js | 2 +- .../discussions/data/hook.js | 6 ++ .../discussions/data/redux.test.js | 97 ++++++++++--------- 9 files changed, 264 insertions(+), 102 deletions(-) create mode 100644 src/pages-and-resources/discussions/data/hook.js diff --git a/src/i18n/index.js b/src/i18n/index.js index d0dc802c3c..2abb2b7cf4 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -1,5 +1,5 @@ import { messages as footerMessages } from '@edx/frontend-component-footer'; - +import { messages as paragonMessages } from '@edx/paragon'; import arMessages from './messages/ar.json'; import frMessages from './messages/fr.json'; import es419Messages from './messages/es_419.json'; @@ -35,5 +35,6 @@ const appMessages = { export default [ footerMessages, + paragonMessages, appMessages, ]; diff --git a/src/pages-and-resources/discussions/DiscussionsSettings.test.jsx b/src/pages-and-resources/discussions/DiscussionsSettings.test.jsx index 73af4b9e0f..9e31c2b498 100644 --- a/src/pages-and-resources/discussions/DiscussionsSettings.test.jsx +++ b/src/pages-and-resources/discussions/DiscussionsSettings.test.jsx @@ -162,10 +162,14 @@ describe('DiscussionsSettings', () => { expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument(); - userEvent.click(queryByText(container, appMessages.backButton.defaultMessage)); + await act(async () => { + userEvent.click(queryByText(container, appMessages.backButton.defaultMessage)); + }); - expect(queryByTestId(container, 'appList')).toBeInTheDocument(); - expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument(); + waitFor(() => { + expect(queryByTestId(container, 'appList')).toBeInTheDocument(); + expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument(); + }); }); test('successfully closes the modal', async () => { @@ -217,14 +221,16 @@ describe('DiscussionsSettings', () => { // content has been loaded - prior to proceeding with our expectations. await waitForElementToBeRemoved(screen.getByRole('status')); - userEvent.click(getByRole(container, 'checkbox', { name: 'Select Discourse' })); - userEvent.click(getByRole(container, 'button', { name: 'Next' })); + await act(async () => userEvent.click(getByRole(container, 'checkbox', { name: 'Select Discourse' }))); + await act(async () => userEvent.click(getByRole(container, 'button', { name: 'Next' }))); - await findByRole(container, 'button', { name: 'Save' }); - userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'key'); - userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret'); - userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test'); - userEvent.click(getByRole(container, 'button', { name: 'Save' })); + waitFor(async () => { + await findByRole(container, 'button', { name: 'Save' }); + userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Key' }), 'key'); + userEvent.type(getByRole(container, 'textbox', { name: 'Consumer Secret' }), 'secret'); + userEvent.type(getByRole(container, 'textbox', { name: 'Launch URL' }), 'http://example.test'); + userEvent.click(getByRole(container, 'button', { name: 'Save' })); + }); await waitFor(() => expect(getByRole(container, 'dialog', { name: 'OK' })).toBeInTheDocument()); }); @@ -364,17 +370,18 @@ describe('DiscussionsSettings', () => { userEvent.click(getByRole(container, 'button', { name: 'Save' })); - await waitFor(() => expect(axiosMock.history.post.length).toBe(1)); - - expect(queryByTestId(container, 'appList')).not.toBeInTheDocument(); - expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument(); + await waitFor(async () => { + expect(axiosMock.history.post.length).toBe(1); + expect(queryByTestId(container, 'appList')).not.toBeInTheDocument(); + expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument(); - // We don't technically leave the route in this case, though the modal is hidden. - expect(window.location.pathname).toEqual(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`); + // We don't technically leave the route in this case, though the modal is hidden. + expect(window.location.pathname).toEqual(`/course/${courseId}/pages-and-resources/discussion/configure/piazza`); - const alert = await findByRole(container, 'alert'); - expect(alert).toBeInTheDocument(); - expect(alert.textContent).toEqual(expect.stringContaining('You are not authorized to view this page.')); + const alert = await findByRole(container, 'alert'); + expect(alert).toBeInTheDocument(); + expect(alert.textContent).toEqual(expect.stringContaining('You are not authorized to view this page.')); + }); }); }); }); @@ -421,17 +428,20 @@ describe.each([ // content has been loaded - prior to proceeding with our expectations. await waitForElementToBeRemoved(screen.getByRole('status')); - userEvent.click(queryByLabelText(container, 'Select Piazza')); - userEvent.click(queryByText(container, messages.nextButton.defaultMessage)); - await waitForElementToBeRemoved(screen.getByRole('status')); + await act(async () => userEvent.click(queryByLabelText(container, 'Select Piazza'))); + await act(async () => userEvent.click(queryByText(container, messages.nextButton.defaultMessage))); - if (showLTIConfig) { - expect(queryByText(container, ltiMessages.formInstructions.defaultMessage)).toBeInTheDocument(); - expect(queryByTestId(container, 'ltiConfigFields')).toBeInTheDocument(); - } else { - expect(queryByText(container, ltiMessages.formInstructions.defaultMessage)).not.toBeInTheDocument(); - expect(queryByTestId(container, 'ltiConfigFields')).not.toBeInTheDocument(); - } + waitFor(async () => { + await waitForElementToBeRemoved(screen.getByRole('status')); + + if (showLTIConfig) { + expect(queryByText(container, ltiMessages.formInstructions.defaultMessage)).toBeInTheDocument(); + expect(queryByTestId(container, 'ltiConfigFields')).toBeInTheDocument(); + } else { + expect(queryByText(container, ltiMessages.formInstructions.defaultMessage)).not.toBeInTheDocument(); + expect(queryByTestId(container, 'ltiConfigFields')).not.toBeInTheDocument(); + } + }); }); }); @@ -477,13 +487,16 @@ describe.each([ // content has been loaded - prior to proceeding with our expectations. await waitForElementToBeRemoved(screen.getByRole('status')); - userEvent.click(queryByLabelText(container, 'Select Piazza')); - userEvent.click(queryByText(container, messages.nextButton.defaultMessage)); - await waitForElementToBeRemoved(screen.getByRole('status')); - if (enablePIISharing) { - expect(queryByTestId(container, 'piiSharingFields')).toBeInTheDocument(); - } else { - expect(queryByTestId(container, 'piiSharingFields')).not.toBeInTheDocument(); - } + await act(async () => userEvent.click(queryByLabelText(container, 'Select Piazza'))); + await act(async () => userEvent.click(queryByText(container, messages.nextButton.defaultMessage))); + + waitFor(() => { + waitForElementToBeRemoved(screen.getByRole('status')); + if (enablePIISharing) { + expect(queryByTestId(container, 'piiSharingFields')).toBeInTheDocument(); + } else { + expect(queryByTestId(container, 'piiSharingFields')).not.toBeInTheDocument(); + } + }); }); }); diff --git a/src/pages-and-resources/discussions/app-list/AppList.jsx b/src/pages-and-resources/discussions/app-list/AppList.jsx index 04509d3a21..3be56eeabe 100644 --- a/src/pages-and-resources/discussions/app-list/AppList.jsx +++ b/src/pages-and-resources/discussions/app-list/AppList.jsx @@ -1,6 +1,10 @@ -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { + useCallback, useEffect, useMemo, useState, useContext, +} from 'react'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { CardGrid, Container, breakpoints } from '@edx/paragon'; +import { + CardGrid, Container, breakpoints, Form, ActionRow, AlertModal, Button, +} from '@edx/paragon'; import { useDispatch, useSelector } from 'react-redux'; import Responsive from 'react-responsive'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; @@ -14,16 +18,25 @@ import messages from './messages'; import FeaturesTable from './FeaturesTable'; import AppListNextButton from './AppListNextButton'; import Loading from '../../../generic/Loading'; +import useIsOnSmallScreen from '../data/hook'; +import { saveProviderConfig, fetchDiscussionSettings } from '../data/thunks'; +import { PagesAndResourcesContext } from '../../PagesAndResourcesProvider'; +import { discussionRestriction } from '../data/constants'; const AppList = ({ intl }) => { const dispatch = useDispatch(); + const { courseId } = useContext(PagesAndResourcesContext); const { - appIds, featureIds, status, activeAppId, selectedAppId, + appIds, featureIds, status, activeAppId, selectedAppId, enabled, postingRestrictions, } = useSelector(state => state.discussions); + + const [showDiscussionAlert, setShowDiscussionAlert] = useState(false); + const [discussionEnabled, setDiscussionEnabled] = useState(enabled); const apps = useModels('apps', appIds); const features = useModels('features', featureIds); const isGlobalStaff = getAuthenticatedUser().administrator; const ltiProvider = !['openedx', 'legacy'].includes(activeAppId); + const isOnSmallcreen = useIsOnSmallScreen(); const showOneEdxProvider = useMemo(() => apps.filter(app => ( activeAppId === 'openedx' ? app.id !== 'legacy' : app.id !== 'openedx' @@ -42,8 +55,49 @@ const AppList = ({ intl }) => { dispatch(updateValidationStatus({ hasError: false })); }, [selectedAppId, activeAppId]); + useEffect(() => { + setDiscussionEnabled(enabled); + }, [enabled]); + + useEffect(() => { + dispatch(fetchDiscussionSettings(courseId, selectedAppId)); + }, [courseId, selectedAppId]); + const handleSelectApp = useCallback((appId) => { dispatch(selectApp({ appId })); + }, []); + + const updateSettings = useCallback((enabledDiscussion) => { + dispatch(saveProviderConfig( + courseId, + selectedAppId, + { + enabled: enabledDiscussion, + postingRestrictions: + enabledDiscussion ? postingRestrictions : discussionRestriction.ENABLED, + }, + )); + }, [courseId, selectedAppId, postingRestrictions, discussionEnabled]); + + const handleClose = useCallback(() => { + setShowDiscussionAlert(false); + setDiscussionEnabled(enabled); + }, [enabled]); + + const handleOk = useCallback(() => { + setShowDiscussionAlert(false); + setDiscussionEnabled(false); + updateSettings(false); + }, []); + + const handleChange = useCallback((e) => { + const toggleVal = e.target.checked; + + setShowDiscussionAlert(toggleVal); + setDiscussionEnabled(!toggleVal); + if (!toggleVal) { + updateSettings(toggleVal); + } }, [selectedAppId]); if (!selectedAppId || status === LOADING) { @@ -72,9 +126,20 @@ const AppList = ({ intl }) => { return (
+ {intl.formatMessage(messages.hideDiscussionTabMessage)} +
+