Compare commits

..

1 Commits

Author SHA1 Message Date
Bilal Qamar
7a54819ca2 feat: upgraded to node v18, added .nvmrc and updated workflows (#471)
* feat: upgraded to node v18, added .nvmrc and updated workflows

* refactor: updated packages

* refactor: resolved eslint issues
2023-06-02 20:35:37 +05:00
18 changed files with 259 additions and 53 deletions

5
.env
View File

@@ -20,5 +20,6 @@ SEGMENT_KEY=''
SITE_NAME=''
USER_INFO_COOKIE_NAME=''
SUPPORT_URL=''
LEARNER_FEEDBACK_URL=''
STAFF_FEEDBACK_URL=''
TA_FEEDBACK_FORM= ''
STAFF_FEEDBACK_FORM= ''
DISPLAY_FEEDBACK_BANNER='false'

View File

@@ -21,5 +21,6 @@ SEGMENT_KEY=''
SITE_NAME=localhost
USER_INFO_COOKIE_NAME='edx-user-info'
SUPPORT_URL='https://support.edx.org'
LEARNER_FEEDBACK_URL=''
STAFF_FEEDBACK_URL=''
TA_FEEDBACK_FORM='https://learner-form.test'
STAFF_FEEDBACK_FORM='https://staff-form.test'
DISPLAY_FEEDBACK_BANNER='false'

View File

@@ -19,5 +19,6 @@ SEGMENT_KEY=''
SITE_NAME='localhost'
USER_INFO_COOKIE_NAME='edx-user-info'
SUPPORT_URL='https://support.edx.org'
LEARNER_FEEDBACK_URL=''
STAFF_FEEDBACK_URL=''
TA_FEEDBACK_FORM='https://learner-form.test'
STAFF_FEEDBACK_FORM='https://staff-form.test'
DISPLAY_FEEDBACK_BANNER='false'

2
.jest/setEnvVars.js Normal file
View File

@@ -0,0 +1,2 @@
process.env.TA_FEEDBACK_FORM= 'https://learner-form.test';
process.env.STAFF_FEEDBACK_FORM= 'https://staff-form.test';

View File

@@ -1,9 +1,9 @@
const { createConfig } = require('@edx/frontend-build');
module.exports = createConfig('jest', {
// setupFilesAfterEnv is used after the jest environment has been loaded. In general this is what you want.
// setupFilesAfterEnv is used after the jest environment has been loaded. In general this is what you want.
// If you want to add config BEFORE jest loads, use setupFiles instead.
setupFiles: ['<rootDir>/.env.test'],
setupFiles: ['<rootDir>/.jest/setEnvVars.js'],
setupFilesAfterEnv: [
'<rootDir>/src/setupTest.js',
],

View File

@@ -1,11 +1,10 @@
@import "~@edx/brand/paragon/fonts.scss";
@import "~@edx/brand/paragon/variables.scss";
@import "~@edx/paragon/scss/core/core.scss";
@import "~@edx/brand/paragon/overrides.scss";
@import "@edx/brand/paragon/fonts.scss";
@import "@edx/brand/paragon/variables.scss";
@import "@edx/paragon/scss/core/core.scss";
@import "@edx/brand/paragon/overrides.scss";
$fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome";
.course-tabs-navigation {
border-bottom: solid 1px #eaeaea;

View File

@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from 'react';
import React, { useContext, useEffect } from 'react';
import camelCase from 'lodash/camelCase';
import { useDispatch, useSelector } from 'react-redux';
@@ -14,7 +14,6 @@ import postsMessages from '../discussions/posts/post-actions-bar/messages';
import { setFilter as setTopicFilter } from '../discussions/topics/data/slices';
const Search = ({ intl }) => {
const [previousSearchValue, setPreviousSearchValue] = useState('');
const dispatch = useDispatch();
const { page } = useContext(DiscussionContext);
const postSearch = useSelector(({ threads }) => threads.filters.search);
@@ -36,7 +35,6 @@ const Search = ({ intl }) => {
dispatch(setSearchQuery(''));
dispatch(setTopicFilter(''));
dispatch(setUsernameSearch(''));
setPreviousSearchValue('');
};
const onChange = (query) => {
@@ -44,7 +42,7 @@ const Search = ({ intl }) => {
};
const onSubmit = (query) => {
if (query === '' || query === previousSearchValue) {
if (query === '') {
return;
}
if (isPostSearch) {
@@ -54,7 +52,6 @@ const Search = ({ intl }) => {
} else if (page === 'learners') {
dispatch(setUsernameSearch(query));
}
setPreviousSearchValue(query);
};
useEffect(() => onClear(), [page]);

View File

@@ -51,7 +51,7 @@ const AuthorLabel = ({
const authorName = (
<span
className={classNames('mr-1.5 font-size-14 font-style font-weight-500 author-name', {
className={classNames('mr-1.5 font-size-14 font-style font-weight-500', {
'text-gray-700': isRetiredUser,
'text-primary-500': !authorLabelMessage && !isRetiredUser,
})}
@@ -99,7 +99,7 @@ const AuthorLabel = ({
{postCreatedAt && (
<span
title={postCreatedAt}
className={classNames('align-content-center', {
className={classNames('font-family-inter align-content-center', {
'text-white': alert,
'text-gray-500': !alert,
})}

View File

@@ -39,7 +39,7 @@ const EndorsedAlertBanner = ({
height: '20px',
}}
/>
<strong className="ml-2">
<strong className="ml-2 font-family-inter">
{intl.formatMessage(isQuestion ? messages.answer : messages.endorsed)}
</strong>
</div>

View File

@@ -8,6 +8,7 @@ import {
import Footer from '@edx/frontend-component-footer';
import { LearningHeader as Header } from '@edx/frontend-component-header';
import { getConfig } from '@edx/frontend-platform';
import { PostActionsBar } from '../../components';
import { CourseTabsNavigation } from '../../components/NavigationBar';
@@ -29,6 +30,7 @@ import BlackoutInformationBanner from './BlackoutInformationBanner';
import DiscussionContent from './DiscussionContent';
import DiscussionSidebar from './DiscussionSidebar';
import useFeedbackWrapper from './FeedbackWrapper';
import InformationBanner from './InformationBanner';
const DiscussionsHome = () => {
const location = useLocation();
@@ -44,6 +46,7 @@ const DiscussionsHome = () => {
const isOnDesktop = useIsOnDesktop();
let displaySidebar = useSidebarVisible();
const enableInContextSidebar = Boolean(new URLSearchParams(location.search).get('inContextSidebar') !== null);
const isFeedbackBannerVisible = getConfig().DISPLAY_FEEDBACK_BANNER === 'true';
const {
courseId, postId, topicId, category, learnerUsername,
} = params;
@@ -92,6 +95,7 @@ const DiscussionsHome = () => {
{!enableInContextSidebar && <Route path={Routes.DISCUSSIONS.PATH} component={NavigationBar} />}
<PostActionsBar />
</div>
{isFeedbackBannerVisible && <InformationBanner />}
<BlackoutInformationBanner />
</div>
{provider === DiscussionProvider.LEGACY && (

View File

@@ -2,7 +2,6 @@ import { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { logError } from '@edx/frontend-platform/logging';
import { RequestStatus } from '../../data/constants';
@@ -23,9 +22,9 @@ export default function useFeedbackWrapper() {
useEffect(() => {
if (configStatus === RequestStatus.SUCCESSFUL) {
let url = getConfig().LEARNER_FEEDBACK_URL;
let url = '//w.usabilla.com/9e6036348fa1.js';
if (isStaff || isUserGroupTA || isCourseAdmin || isCourseStaff) {
url = getConfig().STAFF_FEEDBACK_URL;
url = '//w.usabilla.com/767740a06856.js';
}
try {
// eslint-disable-next-line no-undef

View File

@@ -0,0 +1,64 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Hyperlink, PageBanner } from '@edx/paragon';
import { selectUserIsStaff, selectUserRoles } from '../data/selectors';
import messages from '../messages';
const InformationBanner = ({
intl,
}) => {
const [showBanner, setShowBanner] = useState(true);
const userRoles = useSelector(selectUserRoles);
const isAdmin = useSelector(selectUserIsStaff);
const learnMoreLink = 'https://openedx.atlassian.net/wiki/spaces/COMM/pages/3509551260/Overview+New+discussions+experience';
const TAFeedbackLink = process.env.TA_FEEDBACK_FORM;
const staffFeedbackLink = process.env.STAFF_FEEDBACK_FORM;
const hideLearnMoreButton = ((userRoles.includes('Student') && userRoles.length === 1) || !userRoles.length) && !isAdmin;
const showStaffLink = isAdmin || userRoles.includes('Moderator') || userRoles.includes('Administrator');
return (
<PageBanner
variant="light"
show={showBanner}
dismissible
onDismiss={() => setShowBanner(false)}
>
<div className="font-weight-500">
{intl.formatMessage(messages.bannerMessage)}
{!hideLearnMoreButton
&& (
<Hyperlink
destination={learnMoreLink}
target="_blank"
showLaunchIcon={false}
className="pl-2.5"
variant="muted"
isInline
>
{intl.formatMessage(messages.learnMoreBannerLink)}
</Hyperlink>
)}
<Hyperlink
destination={showStaffLink ? staffFeedbackLink : TAFeedbackLink}
target="_blank"
showLaunchIcon={false}
variant="muted"
className="pl-2.5"
isInline
>
{intl.formatMessage(messages.shareFeedback)}
</Hyperlink>
</div>
</PageBanner>
);
};
InformationBanner.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(InformationBanner);

View File

@@ -0,0 +1,136 @@
import { render, screen } from '@testing-library/react';
import { IntlProvider } from 'react-intl';
import { initializeMockApp } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { initializeStore } from '../../store';
import { DiscussionContext } from '../common/context';
import { fetchConfigSuccess } from '../data/slices';
import messages from '../messages';
import InformationBanner from './InformationBanner';
import '../posts/data/__factories__';
let store;
let container;
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const getConfigData = (isAdmin = true, roles = []) => ({
id: 'course-v1:edX+DemoX+Demo_Course',
userRoles: roles,
hasModerationPrivileges: false,
isGroupTa: false,
isUserAdmin: isAdmin,
});
function renderComponent() {
const wrapper = render(
<IntlProvider locale="en">
<AppProvider store={store}>
<DiscussionContext.Provider value={{ courseId }}>
<InformationBanner />
</DiscussionContext.Provider>
</AppProvider>
</IntlProvider>,
);
container = wrapper.container;
return container;
}
describe('Information Banner learner view', () => {
let element;
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: false,
roles: ['Student'],
},
});
store = initializeStore();
store.dispatch(fetchConfigSuccess(getConfigData(false, ['Student'])));
renderComponent(true);
element = await screen.findByRole('alert');
});
test('Test Banner is visible on app load', async () => {
expect(element).toHaveTextContent(messages.bannerMessage.defaultMessage);
});
test('Test Banner do not have learn more button', async () => {
expect(element).not.toHaveTextContent(messages.learnMoreBannerLink.defaultMessage);
});
test('Test Banner has share feedback button', async () => {
expect(element).toHaveTextContent(messages.shareFeedback.defaultMessage);
});
});
describe('Information Banner moderators/staff/admin view', () => {
let element;
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
store.dispatch(fetchConfigSuccess(getConfigData(true, ['Student', 'Moderator'])));
renderComponent(true);
element = await screen.findByRole('alert');
});
test('Test Banner is visible on app load', async () => {
expect(element).toHaveTextContent(messages.bannerMessage.defaultMessage);
});
test('Test Banner has learn more button', async () => {
expect(element).toHaveTextContent(messages.learnMoreBannerLink.defaultMessage);
});
test('Test Banner has share feedback button', async () => {
expect(element).toHaveTextContent(messages.shareFeedback.defaultMessage);
});
});
describe('User is redirected according to url according to role', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore();
});
test('TAs are redirected to learners feedback form', async () => {
store.dispatch(fetchConfigSuccess(getConfigData(false, ['Student', 'Community TA'])));
renderComponent(true);
expect(screen.getByText(messages.shareFeedback.defaultMessage)
.closest('a'))
.toHaveAttribute('href', process.env.TA_FEEDBACK_FORM);
});
test('moderators/administrators are redirected to moderators feedback form', async () => {
store.dispatch(fetchConfigSuccess(getConfigData(false, ['Student', 'Moderator', 'Administrator'])));
renderComponent(true);
expect(screen.getByText(messages.shareFeedback.defaultMessage)
.closest('a'))
.toHaveAttribute('href', process.env.STAFF_FEEDBACK_FORM);
});
test('user with only isAdmin true are redirected to moderators feedback form', async () => {
store.dispatch(fetchConfigSuccess(getConfigData(true, ['Student'])));
renderComponent(true);
expect(screen.getByText(messages.shareFeedback.defaultMessage)
.closest('a'))
.toHaveAttribute('href', process.env.STAFF_FEEDBACK_FORM);
});
});

View File

@@ -168,6 +168,21 @@ const messages = defineMessages({
defaultMessage: 'anonymous',
description: 'Author name displayed when a post is anonymous',
},
bannerMessage: {
id: 'discussion.banner.welcomeMessage',
defaultMessage: '🎉 Welcome to the new and improved discussions experience!',
description: 'Information banner welcome text',
},
learnMoreBannerLink: {
id: 'discussion.banner.learnMore',
defaultMessage: 'Learn more',
description: 'learn more button to redirect users to know more about new discussion experience ',
},
shareFeedback: {
id: 'discussion.banner.shareFeedback',
defaultMessage: 'Share feedback',
description: 'Share feedback button to open feedback forms',
},
blackoutDiscussionInformation: {
id: 'discussion.blackoutBanner.information',
defaultMessage: 'Posting in discussions is temporarily disabled by the course team',

View File

@@ -32,7 +32,7 @@ const CommentsView = ({
const handleDefinition = (message, commentsLength) => (
<div
className="comment-line mx-4 my-14px text-gray-700 font-style"
className="mx-4 my-14px text-gray-700 font-style"
role="heading"
aria-level="2"
>

View File

@@ -58,9 +58,7 @@ const CommentEditor = ({
const initialValues = {
comment: comment.rawBody,
// eslint-disable-next-line react/prop-types
editReasonCode: comment?.lastEdit?.reasonCode || (
userIsStaff && canDisplayEditReason ? 'violates-guidelines' : undefined
),
editReasonCode: comment?.lastEdit?.reasonCode || (userIsStaff ? 'violates-guidelines' : ''),
};
const handleCloseEditor = (resetForm) => {

View File

@@ -45,8 +45,7 @@ initialize({
config: () => {
mergeConfig({
LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
LEARNER_FEEDBACK_URL: process.env.LEARNER_FEEDBACK_URL,
STAFF_FEEDBACK_URL: process.env.STAFF_FEEDBACK_URL,
DISPLAY_FEEDBACK_BANNER: process.env.DISPLAY_FEEDBACK_BANNER || 'false',
}, 'DiscussionsConfig');
},
},

View File

@@ -1,7 +1,7 @@
@import "~@edx/brand/paragon/fonts.scss";
@import "~@edx/brand/paragon/variables.scss";
@import "~@edx/paragon/scss/core/core.scss";
@import "~@edx/brand/paragon/overrides.scss";
@import "@edx/brand/paragon/fonts.scss";
@import "@edx/brand/paragon/variables.scss";
@import "@edx/paragon/scss/core/core.scss";
@import "@edx/brand/paragon/overrides.scss";
@import "~@edx/frontend-component-footer/dist/footer";
@import "~@edx/frontend-component-header/dist/index";
@@ -65,6 +65,10 @@ $fa-font-path: "~font-awesome/fonts";
font-style: normal;
}
.font-family-inter {
font-family: "Inter";
}
.post-footer-icon-dimentions {
width: 32px !important;
height: 32px !important;
@@ -273,6 +277,7 @@ header {
header {
line-height: 28px;
font-family: Inter, Helvetica Neue, Arial, sans-serif;
font-size: 18px;
height: 60px;
@@ -309,8 +314,10 @@ header {
#courseTabsNavigation {
font-size: 18px;
font-family: Inter, Helvetica Neue, Arial, sans-serif;
.container-xl {
padding-left: 31px;
font-size: 1.125rem;
.nav {
@@ -329,7 +336,7 @@ header {
.header-action-bar {
background-color: #fff;
z-index: 2 !important;
z-index: 2;
box-shadow: 0px 2px 4px rgb(0 0 0 / 15%), 0px 2px 8px rgb(0 0 0 / 15%);
position: sticky;
top: 0;
@@ -338,19 +345,11 @@ header {
.nav-item:not(:last-child){
.nav-link {
border-right: 0;
@media screen and (max-width: 567px) {
border-right: solid 1px #e9e6e4;
}
}
}
}
}
.tox-tinymce-aux {
z-index: 1 !important;
}
.breadcrumb-menu {
z-index: 1;
}
@@ -478,6 +477,7 @@ header {
}
.font-style {
font-family: "Inter";
font-style: normal;
}
@@ -494,11 +494,6 @@ header {
z-index: 1;
}
.comment-line {
width: calc(100% - 180px);
line-height: 1;
}
.post-preview,
.discussion-comments {
blockquote {
@@ -523,8 +518,3 @@ header {
font-size: 18px;
line-height: 28px;
}
.author-name {
line-height: 1;
word-break: break-all;
}