Skip to content

Commit

Permalink
chore: add paragon messages (openedx#530) (openedx#534)
Browse files Browse the repository at this point in the history
Co-authored-by: Mashal Malik <107556986+Mashal-m@users.noreply.github.com>
  • Loading branch information
2 people authored and mariajgrimaldi committed Dec 11, 2023
1 parent 93c34b3 commit 44bc351
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,12 @@ describe('DiscussionsSettings', () => {

expect(queryByTestId(container, 'appConfigForm')).toBeInTheDocument();

userEvent.click(queryByText(container, appMessages.backButton.defaultMessage));
await act(() => userEvent.click(queryByText(container, appMessages.backButton.defaultMessage)));

expect(queryByTestId(container, 'appList')).toBeInTheDocument();
expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument();
await waitFor(() => {
expect(queryByTestId(container, 'appList')).toBeInTheDocument();
expect(queryByTestId(container, 'appConfigForm')).not.toBeInTheDocument();
});
});

test('successfully closes the modal', async () => {
Expand Down
102 changes: 93 additions & 9 deletions src/pages-and-resources/discussions/app-list/AppList.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import React, { useCallback, useEffect } 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 { useModels } from '../../../generic/model-store';
Expand All @@ -13,15 +17,27 @@ 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 [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'
)), [activeAppId]);

// This could be a bit confusing. activeAppId is the ID of the app that is currently configured
// according to the server. selectedAppId is the ID of the app that we _want_ to configure here
Expand All @@ -36,9 +52,48 @@ const AppList = ({ intl }) => {
dispatch(updateValidationStatus({ hasError: false }));
}, [selectedAppId, activeAppId]);

useEffect(() => {
setDiscussionEnabled(enabled);
}, [enabled]);

useEffect(() => {
if (!postingRestrictions) {
dispatch(fetchDiscussionSettings(courseId, selectedAppId));
}
}, [courseId]);

const handleSelectApp = useCallback((appId) => {
dispatch(selectApp({ appId }));
}, [selectedAppId]);
}, []);

const updateSettings = useCallback((enabledDiscussion) => {
dispatch(saveProviderConfig(
courseId,
selectedAppId,
{
enabled: enabledDiscussion,
postingRestrictions:
enabledDiscussion ? postingRestrictions : discussionRestriction.ENABLED,
},
));
}, [courseId, selectedAppId, postingRestrictions]);

const handleClose = useCallback(() => {
setDiscussionEnabled(enabled);
}, [enabled]);

const handleOk = useCallback(() => {
setDiscussionEnabled(false);
updateSettings(false);
}, [updateSettings]);

const handleChange = useCallback((e) => {
const toggleVal = e.target.checked;
setDiscussionEnabled(!toggleVal);
if (!toggleVal) {
updateSettings(!toggleVal);
}
}, [updateSettings]);

if (!selectedAppId || status === LOADING) {
return (
Expand All @@ -55,17 +110,29 @@ const AppList = ({ intl }) => {
}

return (
<div className="my-sm-5 m-1" data-testid="appList">
<h3 className="my-sm-5 my-4">
{intl.formatMessage(messages.heading)}
</h3>
<div className="my-sm-4" data-testid="appList">
<div className={!isOnSmallcreen ? 'd-flex flex-row justify-content-between align-items-center' : 'mb-4'}>
<h3 className={isOnSmallcreen ? 'mb-3' : 'm-0'}>
{intl.formatMessage(messages.heading)}
</h3>
<Form.Switch
floatLabelLeft
className="text-primary-500 align-items-center"
labelClassName="line-height-24"
onChange={handleChange}
checked={!discussionEnabled}
>
Hide discussion tab
</Form.Switch>
</div>
<CardGrid
columnSizes={{
xs: 12,
sm: 6,
lg: 4,
xl: 4,
}}
className={!isOnSmallcreen && 'mt-5'}
>
{apps.map(app => (
<AppCard
Expand All @@ -88,6 +155,23 @@ const AppList = ({ intl }) => {
/>
</div>
</Responsive>
<AlertModal
title={intl.formatMessage(messages.hideDiscussionTabTitle)}
isOpen={enabled && !discussionEnabled}
onClose={handleClose}
isBlocking
className="hide-discussion-modal"
footerNode={(
<ActionRow>
<Button variant="link" className="text-decoration-none bg-black" onClick={handleClose}>Cancel</Button>
<Button variant="primary" className="bg-primary-500 ml-1 rounded-0" onClick={handleOk}>OK</Button>
</ActionRow>
)}
>
<p className="bg-black">
{intl.formatMessage(messages.hideDiscussionTabMessage)}
</p>
</AlertModal>
</div>
);
};
Expand Down
95 changes: 95 additions & 0 deletions src/pages-and-resources/discussions/app-list/AppList.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,102 @@
.pgn__data-table-cell-wrap {
max-width: unset;
}
}
}
}
}

.height-36 {
height: 2.25rem !important;
}

.line-height-20 {
line-height: 1.25rem !important;
}

.font-size-14 {
font-size: 14px !important;
}

.font-weight-500 {
font-weight: 500 !important;
}

.py-7px {
padding: 0 7px;
}

.line-height-24 {
line-height: 24px !important;
}

.hide-discussion-modal {
.pgn__modal-header {
padding-top: 24px;

h2 {
color: $primary-500;
line-height: 28px;
font-size: 22px;
}
}

.bg-black {
color: #000000;
}

.pgn__modal-footer {
padding-top: 8px;
padding-bottom: 24px;
}

button {
font-weight: 500;
}
}

.discussion-restriction {
.unselected-button {
&:hover {
background: #E9E6E4;
}
}

.action-btn {
padding: 10px 16px;
width: 80px;
height: 44px;
font-weight: 500;
font-size: 18px;
line-height: 24px;
}

.w-92 {
width: 92px;
}

.card-body-section {
padding-top: 12px !important;
padding-bottom: 20px !important;
}

.form-control {
border-radius: 0 !important;
font-weight: 400;
font-size: 14px;
line-height: 24px;
}

.collapsible-card {
padding: 14px 14px 14px 24px !important;
min-height: 100px;

.collapsible-trigger {
padding: 0 !important;

.badge {
font-size: 12px;
line-height: 20px;
}
}
}
Expand Down
85 changes: 83 additions & 2 deletions src/pages-and-resources/discussions/app-list/AppList.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,89 @@ describe('AppList', () => {
},
});

store = await initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
test('Successfully shows the disable toggle state of the hide discussion tab by default.', async () => {
renderComponent();

await waitFor(async () => {
const hideDiscussionTab = screen.getByTestId('hide-discussion');

expect(hideDiscussionTab).not.toBeChecked();
});
});

test.each([
{ title: 'Ok', description: 'Enable the toggle state by clicking on OK button' },
{ title: 'Cancel', description: 'Disable the toggle state by clicking on Cancel button' },
])('%s of the hide discussion tab', async ({ title }) => {
renderComponent();

await waitFor(async () => {
let hideDiscussionTab = screen.getByTestId('hide-discussion');

await act(async () => {
fireEvent.click(hideDiscussionTab);
});

const actionButton = screen.queryByText(title);

await act(async () => {
fireEvent.click(actionButton);
});

hideDiscussionTab = screen.getByTestId('hide-discussion');

if (title === 'OK') {
expect(hideDiscussionTab).toBeChecked();
} else {
expect(hideDiscussionTab).not.toBeChecked();
}
});
});

test('display a card for each available app', async () => {
renderComponent();

await waitFor(async () => {
const appCount = await store.getState().discussions.appIds.length;
expect(screen.queryAllByRole('radio')).toHaveLength(appCount);
});
});

test('displays the FeaturesTable at desktop sizes', async () => {
renderComponent();
await waitFor(() => expect(screen.queryByRole('table')).toBeInTheDocument());
});

test('hides the FeaturesTable at mobile sizes', async () => {
renderComponent(breakpoints.extraSmall.maxWidth);
await waitFor(() => expect(screen.queryByRole('table')).not.toBeInTheDocument());
});

test('hides the FeaturesList at desktop sizes', async () => {
renderComponent();
await waitFor(() => expect(screen.queryByText(messages['supportedFeatureList-mobile-show'].defaultMessage))
.not.toBeInTheDocument());
});

test('displays the FeaturesList at mobile sizes', async () => {
renderComponent(breakpoints.extraSmall.maxWidth);

await waitFor(async () => {
const appCount = await store.getState().discussions.appIds.length;
expect(screen.queryAllByText(messages['supportedFeatureList-mobile-show'].defaultMessage))
.toHaveLength(appCount);
});
});

test('selectApp is called when an app is clicked', async () => {
renderComponent();

await waitFor(() => {
userEvent.click(screen.getByLabelText('Select Piazza'));
const clickedCard = screen.getByRole('radio', { checked: true });
expect(within(clickedCard).queryByLabelText('Select Piazza')).toBeInTheDocument();
});
});
});

const mockStore = async (mockResponse, screenWidth = breakpoints.extraLarge.minWidth) => {
Expand Down
20 changes: 20 additions & 0 deletions src/pages-and-resources/discussions/app-list/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,26 @@ const messages = defineMessages({
defaultMessage: 'Commonly requested',
description: 'The type of a discussions feature.',
},
hideDiscussionTabTitle: {
id: 'authoring.discussions.hide-tab-title',
defaultMessage: 'Hide the discussion tab?',
description: 'Title message to hide discussion tab',
},
hideDiscussionTabMessage: {
id: 'authoring.discussions.hide-tab-message',
defaultMessage: 'The discussion tab will no longer be visible to learners in the LMS. Additionally, posting to the discussion forums will be disabled. Are you sure you want to proceed?',
description: 'Help message to hide discussion tab',
},
hideDiscussionOkButton: {
id: 'authoring.discussions.hide-ok-button',
defaultMessage: 'Ok',
description: 'Ok button title',
},
hideDiscussionCancelButton: {
id: 'authoring.discussions.hide-cancel-button',
defaultMessage: 'Cancel',
description: 'Cancel button title',
},
});

export default messages;
2 changes: 1 addition & 1 deletion src/pages-and-resources/discussions/data/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ function denormalizeData(courseId, appId, data) {

const apiData = {
context_key: courseId,
enabled: true,
enabled: data.enabled,
lti_configuration: ltiConfiguration,
plugin_configuration: pluginConfiguration,
provider_type: appId,
Expand Down
Loading

0 comments on commit 44bc351

Please sign in to comment.